Node中最多见的内置模块 Filesystem(fs), 该模块提供了处理文件和目录的函数node
// 异步版本 var fs = requier('fs') fs.readFile('file.text', 'utf8', function(error, text) { if(error) { throw error } console.log('The file contained:', text) }) //同步版本 fs.readFileSync('file.text', 'utf8')
异步回调函数中一般遵循异步优先, 即第一个参数接收可能错误的对象, 其他参数接收结果
使用同步函数较为节省代码,再简单的脚本中很是适用,可是异步函数会带来额外的速度提高,减小延迟面试
硬连接是指经过索引节点来进行连接。在Linux的文件系统中,全部文件都会分配一个索引节点编号Inode,它是文件在系统中的惟一标识,文件的实际数据放置在数据区域(data block),INode存储着文件参数信息(元数据metadata),好比建立时间、修改时间、文件大小、属主、归属的用户组、读写权限、数据所在block号等,多个文件名能够指向同一索引节点(Inode)。windows
硬连接只能在同一文件系统(盘)中的文件之间进行连接,不能对目录进行建立。只要文件的索引节点还有一个以上的连接,其中一个连接更改文件内容,其他连接读取文件内容也发生改变,只删除其中一个连接并不影响索引节点自己和其余的连接(数据的实体并未删除),只有当最后一个连接被删除后,此时若是有新数据要存储到磁盘上,被删除的文件的数据块及目录的连接才会被释放,空间被新数据暂用覆盖。数组
软连接(也叫符号连接),相似于windows系统中的快捷方式,与硬连接不一样,软连接就是一个普通文件,只是数据块内容有点特殊,文件用户数据块中存放的内容是另外一文件的路径名的指向,经过这个方式能够快速定位到软链接所指向的源文件实体。源文件删除,软连接也会失效,软连接能够跨文件系统对文件或目录建立。promise
执行 readFile 函数 返回Promisedom
var fs = require('fs') function readFilePromise(...args) { return new Promise((resolve, reject) => { fs.readFile(...args, (err, data) => { if (err) { reject(err) } else { resolve(data) } }) }) } readFilePromise('a.js').then(data => { }).catch(err => { })
执行 writeFile 函数 返回Promise异步
var fs = require('fs') function writeFilePromise(...args) { return new Promise((resolve, reject) => { fs.writeFile(...args, (err) => { if (err) { resolve(err) } else { reject() } }) }) } writeFilePromise('a.js').then(data => { }).catch(err => { })
将一个基于回调的函数转为一个返回 Promise 的函数函数
function promisify(callbackBasedFunction) { return function(...args) { return new Promise((resolve, reject) => { callbackBasedFunction(...args,(err, data) => { if (err) { reject(err) } else { resolve(data) } }) }) } } readFilePromise = promisify(fs.readFile) writeFilePromise = promisify(fs.writeFile) statunlinkPromise = promisify(fs.stat) unlinkPromise = promisify(fs.unlink)
将一个基于 Promise 的函数转为一个返回回调的函数优化
function callbackify(promiseBased) { return function (...args) { var cb = args.pop() promiseBased(...args).then(val => { cb(null, val) }, reason => { cb(reason) }) } }
固然啦, 这两个函数在标准库中已经集成好了, 就是 utilsui
var fs = require('fs') var utils = require('utils') var readFilePromise = utils.promisify(fs.readFile) var readFile = utils.callbackify(readFilePromise)
一个一个转仍是有点麻烦, 如今的 node 已经提供了promise 版本的 fs 模块
fs = require('fs').promises fs.readFile('a.txt').then().catch()
接收一个文件夹路径,返回这个文件夹里面的全部文件名,须要递归的获得全部的文件名 并放在一个一维数组里返回
须要写三个版本:
const fs = require('fs') const fsp = fs.promises function listAllFilesSync(path) { var stat = fs.statSync(path) var result = [] if (stat.isFile()) { return [path] // 若是路径类型是文件,直接返回 } else { var entries = fs.readdirSync(path, { withFileTypes: true }) // 读取全部文件, withFileTypes 生成的数组将填充 fs.Dirent 对象,而不是字符串 entries.forEach(entry => { var fullPath = path + '/' + entry.name // 新的路径为原来的path接上新的文件夹名 var files = listAllFilesSync(fullPath) // 递归, 返回的数组全都push到result中 result.push(...files) }); return result } } console.log(listAllFilesSync('./'))
function listAllFilesPromise(path) { return fsp.stat(path).then(stat => { if (stat.isFile()) { return [path] } else { return fsp.readdir(path, {withFileTypes: true}).then(entries => { return Promise.all(entries.map(entry => { return listAllFilesPromise(path + '/' + entry.name) })).then(arrays => { return [].concat(...arrays) }) }) } }) } listAllFilesPromise('./').then(console.log)
function listAllFilesCallback(path, callback) { fs.stat(path, (err, stat) => { if (stat.isFile()) { callback([path]) } else { fs.readdir(path, {withFileTypes: true}, (err, entries) => { var result = [] var count = 0 if (entries.length == 0) { callback([]) // 当文件夹为空时,直接返回,不走forEach } entries.forEach((entry, i) => { var fullPath = path + '/' + entry.name listAllFilesCallback(fullPath, (files) => { result[i] = files count++ if (count == entries.length) { callback([].concat(...result)) } }) }) }) } }) } listAllFilesCallback('./', console.log)
以上的三种方法: 同步版本时间效率不高, promise 版本 return 过多比较繁琐, 异步版本 return 很少 可是嵌套层级不灵活
有没有一种方法能够兼具三者的优势呢?
有! 将生成器函数和promise函数组合, 便可优化promise异步代码的书写
function * natureNumber(n) { for(var i = 0; i < n; i++) { var x = yield i console.log(x) } } var iter = natureNumber(5) iter.next() // {value: 0, done: false} iter.next(88) // 88 {value: 1, done: false} iter.next(99) // 00 {value: 2, done: false} iter.return(111) // {value: 111, done: true}
咱们先用一个 xhr 结合生成器函数举例
function get(url) { return new Promise(resolve => { var xhr = new XMLHttpRequest() xhr.open('get', url) xhr.onload = () => resolve(xhr.responseText) xhr.send() }) } function * foo () { var x = yield get('/') console.log(x) } var iter = foo() obj = iter.next() // {value: Promise, done: false} obj.value.then(val => iter.next(val)) // 返回页面内容
yield的优势是能够在执行中等待, 等待的时间由函数执行时间决定
为了方便调试,接下来的举例会用setTimeout来模拟一个异步的请求,当返回多个值时:
function squareAsync(x) { return new Promise(resolve => { setTimeout(() => resolve(x * x), 1000 + Math.random() * 2000) }) } function * foo () { var x = yield squareAsync(2) console.log(x) var y = yield squareAsync(3) console.log(y) var z = yield squareAsync(4) console.log(z) } var iter = foo() iter.next().value.then(val =>{ // yield 返回 promise iter.next(val).value.then(val =>{ // 4 iter.next(val).value.then(val =>{ // 9 iter.next(val) // 16 }) }) })
是否是发现了点什么, 这么写重复度有点高,能够进行封装:
var iter = foo() var generated = iter.next() start() function start(val) { if (!generated.done) { generated.value.then(val => { generated = iter.next(val) start(val) }) } }
异步递归, 把一个老是生成 promise 的生成器函数, 在 promise 执行时等待, 然后 promise 获得结果以后继续执行, 并将 promise 的结果赋值给变量。 那么, 若是 promise 返回失败了怎么办?
function * foo () { var x = yield squareAsync(2) console.log(x) try { var y = yield Promise.reject(3) console.log(y) } catch(e) { console.log('error', e) } var z = yield squareAsync(4) console.log(z) } var iter = foo() var generated = iter.next() step() function step() { if (!generated.done) { generated.value.then(val => { generated = iter.next(val) step() }, reason => { generated = iter.throw(reason) step() }) } }
利用这种方式, 能够将异步的函数写成相似同步的方式, 在代码可读性上有了很大的提高, 固然了还能够再进行封装, 方便使用, 同时能够将分步执行的函数也做为promise执行, 这样在全部分步执行的步骤结束后咱们也能够拿到一个返回值:
run(function * foo () { var x = yield squareAsync(2) console.log(x) try { var y = yield Promise.reject(3) console.log(y) } catch(e) { console.log('error', e) } var z = yield squareAsync(4) console.log(z) }).then((val) => { console.log(val) }) function run(generatorFunction) { return new Promise((resolve, reject) => { var iter = generatorFunction() var generated = iter.next() step() function step() { 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是生成器函数的返回值, 同时返回的函数也有多是promise,因此返回一个promise } } }) }
接下来, 还有最后一种状况须要考虑, 即在生成器函数中没有 try 应该怎么办, 好比下面这样:
run(function * foo () { var x = yield squareAsync(2) console.log(x) try { var y = yield Promise.reject(3) console.log(y) } catch(e) { console.log('error', e) } var m = yield Promise.reject(4)// yield 抛出错误,可是没有包try console.log(m) var z = yield squareAsync(5) console.log(z) }).then((val) => { console.log(val) })
在这种状况下, yield的错误会抛出到foo函数的外面, 若是不处理的话颇有可能就跑到控制台去了。 固然不止这一种状况可能出错, 好比 console.log(m) 不当心写成了 console.log(m(), 会在next的时候产生执行错误, 此要想解决错误, 应该在run中调用try/catch
function run(generatorFunction) { return new Promise((resolve, reject) => { var iter = generatorFunction() var generated try { generated = iter.next() step() } catch(e) { reject(e) } function step() { 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) } } }) }
上面这段代码, 在面试中有可能会被问到, 须要能写出来刚刚咱们只是考虑生成器函数调用普通函数, 若是生成器函数调用的是另外一个生成器函数应该怎么写呢, 那个有点复杂, 咱们如今先不考虑。