当前位置: 首页 > 后端技术 > Node.js

Node入门(二)---文件系统模块(上)

时间:2023-04-03 21:18:30 Node.js

文件系统模块Node中最常见的内置模块Filesystem(fs),该模块提供了处理文件和目录的函数readFile:读取文件,并将文件内容传递给函数writeFile:将文件写入磁盘readdir:以字符串数组的形式返回目录中的文件stat:用于获取文件信息rename:用于重命名文件unlink:用于删除文件//异步版本varfs=requier('fs')fs.readFile('file.text','utf8',function(error,text){if(error){throwerror}console.log('Thefilecontained:',text)})//同步版本fs.readFileSync('file.text','utf8')异步回调函数通常遵循异步优先级,即第一个参数接收一个可能出错的对象,其余参数使用同步函数接收结果。节省代码,非常适合简单的脚本,但是异步函数会带来额外的速度提升和减少延迟链接分类硬链接(HardLink)硬链接是指通过索引节点链接。在Linux文件系统中,所有文件都会分配一个索引节点号inode,它是文件在系统中的唯一标识。文件的实际数据放在数据区(datablock),INode存储文件参数信息(metadata元数据),如创建时间、修改时间、文件大小、所有者、所属用户组、读写权限等,数据所在的块号等。多个文件名可以指向同一个索引节点(Inode)。硬链接只能在同一文件系统(磁盘)内的文件之间进行链接,不能创建目录。只要文件的索引节点有多个链接,其中一个链接改变文件的内容,其他链接的内容读取文件。只删除其中一个链接不影响索引节点本身和其他链接(数据的实体没有改变。删除),只有在删除最后一个链接时,如果此时有新的数据要存入磁盘时间,被删除文件的数据块和目录的链接将被释放,空间将被新数据暂时覆盖。软链接(SymbolicLink)软链接(也叫符号链接)类似于windows系统中的快捷方式。与硬链接不同,软链接是一个普通的文件,只是数据块的内容有点特殊,用户数据块中存放的文件内容是另一个文件的路径名的指向。这样可以快速定位到软链接指向的源文件实体。如果源文件被删除,软链接也会失效。可以跨文件系统为文件或目录创建软链接。扩展执行readFile函数返回Promisevarfs=require('fs')functionreadFilePromise(...args){returnnewPromise((resolve,reject)=>{fs.readFile(...args,(err,data)=>{if(err){reject(err)}else{resolve(data)}})})}readFilePromise('a.js').then(data=>{}).catch(err=>{})执行writeFile函数returnPromisevarfs=require('fs')functionwriteFilePromise(...args){returnnewPromise((resolve,reject)=>{fs.writeFile(...args,(err)=>{if(err){resolve(err)}else{reject()}})})}writeFilePromise('a.js').then(data=>{}).catch(err=>{})将基于acallback将函数转换为返回Promise函数的函数)=>{if(err){reject(err)}else{resolve(data)}})})}}readFilePromise=promisify(fs.readFile)writeFilePromise=promisify(fs.writeFile)statunlinkPromise=promisify(fs.stat)unlinkPromise=promisify(fs.unlink)将基于Promise的函数转换为返回回调函数的函数callbackify(promiseBased){返回函数(...args){varcb=args.pop()promiseBased(...args).then(val=>{cb(null,val)},reason=>{cb(reason)})}}当然,这两个函数已经集成在标准库中,即utilsvarfs=require('fs')varutils=require('utils')varreadFilePromise=utils.promisify(fs.readFile)varreadFile=utils.callbackify(readFilePromise)一个一个转还是有点麻烦。现在node已经提供了fs模块的promise版本fs=require('fs').promisesfs.readFile('a.txt').then().catch()实例接收文件夹路径并返回所有文件名在这个文件夹中。需要递归获取所有的文件名,放到一个一维数组中返回。需要写三个版本:同步版本callbackversionPromiseversion同步版本constfs=require('fs')constfsp=fs.promisesfunctionlistAllFilesSync(path){varstat=fs.statSync(path)varresult=[]if(stat.isFile()){return[path]//如果路径类型是文件,直接返回}else{varentries=fs.readdirsync(path,{withFileTypes:true})//读取所有文件,withFileTypes生成的数组将填充fs.Dirent对象,而不是字符串entries.forEach(entry=>{varfullPath=path+'/'+entry.name//新路径为原路径后接新文件夹名varfiles=listAllFilesSync(fullPath)//递归,将所有返回的数组推送到resultresult.push(...files)});返回结果}}console.log(listAllFilesSync('./'))PromiseversionfunctionlistAllFilesPromise(path){returnfsp.stat(path).then(stat=>{if(stat.isFile()){return[path]}else{returnfsp.readdir(path,{withFileTypes:true}).then(entries=>{returnPromise.all(entries.map(entry=>{returnlistAllFilesPromise(path+'/'+entry.name)})).then(arrays=>{return[].concat(...arrays)})})}})}listAllFilesPromise('./').then(console.log)异步版本函数listAllFilesCallback(path,callback){fs.stat(path,(err,stat)=>{if(stat.isFile()){callback([path])}else{fs.readdir(path,{withFileTypes:true},(err,entries)=>{varresult=[]varcount=0if(entries.length==0){callback([])//当file文件夹为空时直接返回而不是forEach}entries.forEach((entry,i)=>{varfullPath=path+'/'+entry.namelistAllFilesCallback(fullPath,(files)=>{result[i]=filescount++if(count==entries.length){callback([].concat(...result))}})})})}})}listAllFilesCallback('./',console.log)提升上面一共有三种方式:同步版时间效率低,promise版返回过多繁琐,异步版返回少但嵌套层次不灵活。有没有一种方法可以结合三者的优点呢?是的!将generator函数和promise函数结合,可以优化promise异步代码的编写。生成器函数简单回顾:生成器函数可以在执行过程中暂停,后面可以从暂停继续执行。使用function*声明next()方法返回一个对象,包含两个属性:value和done。value属性表示这次yield表达式的返回值。done属性是布尔类型,表示生成器函数是否执行完毕,在调用next()方法时返回。如果传入参数,那么这个参数会被传递给最后执行的yield语句左边的变量。当调用return时,生成器会立即变为完成状态,即done为真。如果return后面跟着一个值,那么这个值将被用作当前调用next()方法的返回值。function*natureNumber(n){for(vari=0;i{varxhr=newXMLHttpRequest()xhr.open('get',url)xhr.onload=()=>resolve(xhr.responseText)xhr.send()})}function*foo(){varx=yieldget('/')console.log(x)}variter=foo()obj=iter.next()//{value:Promise,done:false}obj.value.then(val=>iter.next(val))//返回页面的优势contentyield可以在执行过程中等待。等待时间由函数的执行时间决定。为了方便调试,下例将使用setTimeout来模拟异步请求。返回多个值时:functionsquareAsync(x){returnnewPromise(resolve=>{setTimeout(()=>resolve(x*x),1000+Math.random()*2000)})}function*foo(){varx=yieldsquareAsync(2)console.log(x)vary=yieldsquareAsync(3)console.log(y)varz=yieldsquareAsync(4)console.log(z)}variter=foo()iter.next().value.then(val=>{//yield返回promiseiter.next(val).value.then(val=>{//4iter.next(val).value.then(val=>{//9iter.next(val)//16})})})你找到什么了吗?这样写重复度有点高,可以封装一下:variter=foo()vargenerated=iter.next()start()functionstart(val){if(!generated.done){generated.value.then(val=>{generated=iter.next(val)start(val)})}}异步递归,放一个一直生成promise的generator函数,等待promise执行完,然后继续执行promise获取结果,并将promise的结果赋值给变量那么,如果promise失败了怎么办?function*foo(){varx=yieldsquareAsync(2)console.log(x)try{vary=yieldPromise.reject(3)console.log(y)}catch(e){console.log('error',e)}varz=yieldsquareAsync(4)console.log(z)}variter=foo()vargenerated=iter.next()step()functionstep(){if(!generated.done){generated.value.then(val=>{generated=iter.next(val)step()},reason=>{generated=iter.throw(reason)step()})}}使用这个方法,异步函数可以采用类似同步的方式编写,大大提高了代码的可读性。当然也可以封装起来方便使用。同时,分步执行的函数也是作为一个promise来执行的,这样我们在分步执行完所有的步骤后也能得到一个返回值:run(function*foo(){varx=yieldsquareAsync(2)console.log(x)try{vary=yieldPromise.reject(3)console.log(y)}catch(e){console.log('error',e)}varz=yieldsquareAsync(4)console.log(z)}).then((val)=>{console.log(val)})functionrun(generatorFunction){returnnewPromise((resolve,reject)=>{var迭代器=generatorFunction()vargenerated=iter.next()step()functionstep(){if(!generated.done){generated.value.then(val=>{generated=iter.next(val)step()},reason=>{generated=iter.throw(reason)step()})}else{Promise.resolve(generated.value).then(resolve,reject)//此时的generated.value为返回值generatorfunction,返回的函数也可能是promise,所以返回一个promise}}})}接下来还有最后一种情况要考虑,就是generator函数里面没有try怎么办,比如如下所示:run(function*foo(){varx=yieldsquareAsync(2)console.log(x)try{vary=yieldPromise.reject(3)console.log(y)}catch(e){console.log('error',e)}varm=yieldPromise.reject(4)//yield抛出错误,但是没有包tryconsole.log(m)varz=yieldsquareAsync(5)console.log(z)}).then((val)=>{console.log(val)})这种情况下,yield错误会被foo函数抛出,如果不处理,很可能去控制台当然,错误的情况可能不止一种。比如console.log(m)不小心写成了console.log(m(),下次执行就会出错,解决这个错误应该在runcatchfunction中调用try/run(generatorFunction){returnnewPromise((resolve,reject)=>{variter=generatorFunction()vargeneratedtry{generated=iter.next()step()}catch(e){reject(e)}functionstep(){if(!generated.done){generated.value.then(val=>{try{generated=iter.next(val)step()}catch(e){reject(e)}},reason=>{try{generated=iter.throw(reason)step()}catch(e){reject(e)}})}else{Promise.resolve(generated.value).then(resolve,reject)}}})}上面的代码可能会被问到面试的时候需要能写出来,刚才我们只是考虑生成器函数调用了一个普通的函数,如果生成器函数又调用了另外一个生成器函数,应该怎么写呢,有点复杂,咱不说现在考虑一下。