27

[译] Node.js核心模块 --- fs

精通Node.js 核心模块 ----- fs

原文链接: [Mastering the Node.js Core Modules - The File System & fs Module ]https://blog.risingstack.com/mastering-the-nodejs-core-modules-file-system-fs-module/()

在这篇文章中, 我们将看一下 文件系统 这个核心模块, File Streams 以及 一些其他 fs 模块可选的

想象一下, 你刚刚获得一份工作, 在这项工作中, 你必须使用Node.js 完成一个工作, 你面对的问题似乎是很简单的, 但是你没有去检查Node.js 的官方文档,

相反, 你去google可以替你完成工作的模块.

尽管这个事情是完全ok的, 但是又是这些核心模块可以很容易地做到这些事情为你.

在这篇新的 精通Node.js 核心模块系列文章中, 你可以学习这些核心模块有哪些隐藏的特性, 并且你可以如何使用他们.我们也将会提到一些能使你的日常开发顺畅以及

扩展这些核心模块的模块.

Node.js fs 模块

文件 I/O , 只是简单地包装了标准的POSIX函数. 如果要使用 fs 模块, 需要手动引入这个模块, 所有的方法均有同步和异步的形式.

异步API

const fs = require('fs')

fs.unlink('/tmp/hello', (err) => {
    if (err) {
        return console.log(err)
    }

    console.log('successfully delete /tmp/hello')
}

你应该总是使用异步的api去开发产品, 因为它不会阻塞事件循环, 所以你可以构建性能高的应用

同步API

const fs = require('fs')

try{
    fs.unlinkSync('/tmp/hello')
} catch (err) {
    console.log(err)
}

console.log('successfully deleted /tmp/hello ')

你应该只在构建一个概念app或者下的命令行 应用的时候使用 同步API

Node.js File streams

我们经常见到的一件事情是很少有开发者去利用这个 file stream

什么是Node.js streams, 有啥用?

streams 是一级构造在node.js中处理数据的时候, 在理解的时候有三个需要理解的概念:

  • source: 你处理的数据来源的地方的对象
  • pipeline: 你处理的数据会经过的地方
  • sink: 你处理的数据停止的地方

要想了解更多的有关于Stream 的信息, 请看 Substack HandBook

因为核心的fs模块暴露一个特性去复制文件, 以你可以简单地使用streams来做这件事情:

const fs = require('fs')

const readableStream = fs.createReadStream('original.txt')
var writableStream = fs.createWriteStream('copy.txt')

readableStream.pipe(writableStream)

你可以问, 为什么我应该使用它, 它仅仅是一个 cp 命令的翻版.

最大的好处在这个场景中是容易转换这个文件, 你可以很容易的做一些事情像下面这样的去压缩一个文件:

const fs = require('fs')
const zlib = require('zlib')

fs.createReadableStream('original.txt.gz')
    .pipe(zlib.createGunzip())
    .pipe(fs.createWritableStream('original.txt'))

什么时候不应该使用 fs.access

fs.access方法的目的是去检查用户是否有某个文件或路径的权限, 就像这样的:

fs.access('/etc/password', fs.constants.R_OK | fs.constants.W_OK, (err) => {
  if (err) {
     return console.error('no access')
  }

  console.log('access for read/write')
})

暴露出来的权限常量:

  • fs.constants.F_OK 检查路径是否是对正在调用的进程可见的
    • fs.constants.R_OK 检查路径是否能够被进程读
    • fs.constants.W_OK 检查路径是否能够被进程写
    • fs.constants.X_OK 检查路径是否能够被进程执行

然而, 请注意使用fs.access检查一个文件的可接触性在调用fs.open, fs.readFile , fs.writeFile 是不推荐的.

原因很简单, 如果你这么做了, 那么你将会引入一个竞争场景, 在你检查和真实文件操作的中间, 很可能有其他的进程正在

更改这个文件.

相反, 你应该直接打开文件, 然后处理错误的例子.

关于 fs.watch 的警告

使用 fs.watch 方法, 你可以监听一个文件或者文件夹是否变化了

然而, fs.watch API 并不是 100% 跨平台的, 并且在一些文件系统中, 它是完全不可用的:

  • 在Linux 系统, 这个操作使用的是 inotify
  • 在BSD 系统, 这个方法使用的是 kqueue
  • 在OSX 系统, 这个方法使用 kqueue 来检测文件, 使用 FSEvents来检测文件夹
  • 在SunOS系统, 这个方法使用 event ports
  • 在windows系统上, 这个特性依赖于 ReadDirectoryChangesW

需要注意的是, 递归操作选项仅仅支持 OSX和 windows系统, 在Linux上不支持的

除此之外, fileName 这个参数在watch的回调中不总是会被提供的(它仅仅支持Linux 和 windows)操作系统, 所以你要准备好他是undefined的状态.

fs.watch('some/path', (eventType, fileName) => {
    if (!fileName) {
        // fileName is  missing, handle it gracefully
    }
})

来自npm 的一些有用的fs 模块

有一些非常有用的模块由社区提供的并且扩展了fs模块功能

graceful-fs

这个graceful-fs 是一个核心fs模块的替代品, 有一些改进的地方:

  • 可以使得 openreaddir 队列化, 并且可以重新操作当有一个 EMFILE错误时, 因为有太多文件描述符的
  • 可以忽略EINVALEPERM错误在 chown, fchown或者lchown操作时, 如果用户不是一个root用户的话.
  • 可以使得 lchmodlchown变成noops, 当它不可用时
  • 重新读取一个文件 当读取一个文件的结果是 EAGAIN错误时.

    你可以开始使用它就像核心fs模块时, 或者通过打包全局模块

const fs = require('graceful-fs')

const originalFs = require('fs')
const gracefulFs = require('graceful-fs')

gracefulFs.gracefulify(originalFs)

mock-fs

这个模块允许Node的内建模块 fs 临时消失, 通过一个在内存中的仿文件操作系统. 这个模块允许你运行一些测试通过模仿

文件或者目录.

开始使用这个模块也是相当容易的:

    const mock = require('mock-fs')
    const fs = require('fs')

    mock({
        'path/to/fake/dir': {
            'some-file.txt': 'file content here',
            'empty-dir': {}
        },
        'path/to/some.png': new Buffer([8, 6, 7, 5, 3, 0,9])
    })

    fs.exists('path/to/fake/dir', function(exists) {
        console.log(exists)

        // will output here
    })

lockfile

文件加锁是一种限制只有一个进程可以使用文件的一种方式, 这样的话, 可以避免竞争.

使用这个模块也是很容易的:

const lockFile = require('lockfile')

lockFile.lock('some-file.lock', function (err) {
      // if the err happens, then it failed to acquire a lock.
  // if there was not an error, then the file was created,
  // and won't be deleted until we unlock it.

  // then, some time later, do:

  lockFile.unlock('some-file.lock', function (err) {
        // some action
  })
})