node 初步 (二)---文件系统模块(上)

文件系统模块

Node中最多见的内置模块 Filesystem(fs), 该模块提供了处理文件和目录的函数node

  • readFile: 读取文件,并将文件内容传递给函数
  • writeFile: 将文件写到磁盘上
  • readdir: 将目录中的文件以字符串数组的方式返回
  • stat: 用于获取文件信息
  • rename: 用于重命名文件
  • unlink: 用于删除文件
// 异步版本
   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')

异步回调函数中一般遵循异步优先, 即第一个参数接收可能错误的对象, 其他参数接收结果
使用同步函数较为节省代码,再简单的脚本中很是适用,可是异步函数会带来额外的速度提高,减小延迟面试

连接分类

硬连接(Hard Link)

硬连接是指经过索引节点来进行连接。在Linux的文件系统中,全部文件都会分配一个索引节点编号Inode,它是文件在系统中的惟一标识,文件的实际数据放置在数据区域(data block),INode存储着文件参数信息(元数据metadata),好比建立时间、修改时间、文件大小、属主、归属的用户组、读写权限、数据所在block号等,多个文件名能够指向同一索引节点(Inode)。windows

硬连接只能在同一文件系统(盘)中的文件之间进行连接,不能对目录进行建立。只要文件的索引节点还有一个以上的连接,其中一个连接更改文件内容,其他连接读取文件内容也发生改变,只删除其中一个连接并不影响索引节点自己和其余的连接(数据的实体并未删除),只有当最后一个连接被删除后,此时若是有新数据要存储到磁盘上,被删除的文件的数据块及目录的连接才会被释放,空间被新数据暂用覆盖。数组

软连接(Symbolic Link)

软连接(也叫符号连接),相似于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()

实 例

接收一个文件夹路径,返回这个文件夹里面的全部文件名,须要递归的获得全部的文件名 并放在一个一维数组里返回
须要写三个版本:

  • 同步版
  • 回调版
  • Promise版本

同步版

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('./'))

Promise版本

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 * 声明
  • next()方法返回一个对象,包含两个属性:value 和 done,value 属性表示本次 yield 表达式的返回值,done 属性为布尔类型,表示生成器函数是否已经执行完毕并返回。
  • 调用 next()方法时,若是传入了参数,那么这个参数会传给上一条执行的 yield语句左边的变量
  • 当调用 return 时,会致使生成器当即变为完成状态,即 done 为 true。若是 return 后面跟了一个值,那么这个值会做为当前调用 next() 方法返回的 value 值。
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)
      }
    }
  })
}

上面这段代码, 在面试中有可能会被问到, 须要能写出来刚刚咱们只是考虑生成器函数调用普通函数, 若是生成器函数调用的是另外一个生成器函数应该怎么写呢, 那个有点复杂, 咱们如今先不考虑。

相关文章
相关标签/搜索