createreadstream是异步的吗?

12
2个回答

40

fs.createReadStream()是异步的吗?

是和不是。这个问题实际上更多的是语义上的问题,因为它在同步的外观下隐藏了一个异步操作。fs.createReadStream()看起来具有同步接口。它不返回Promise或接受回调来在运行结束后进行通信或发送一些结果。因此,从接口上看,它似乎是同步的。我们知道在使用它时没有需要等待的事情才能开始使用流。所以你可以像使用同步接口一样使用它。

这是fs.createReadStream()的签名:

fs.createReadStream(path[, options])

而在选项对象中没有回调选项,也没有提到返回的Promise。这不是一个典型的异步接口。


另一方面,如果您查看fs.rename()的签名:

fs.rename(oldPath, newPath, callback)

你可以看到,它需要一个回调函数,文档中称之为“完成回调函数”,这个函数显然是异步的。


但是,fs.createReadStream()会打开一个文件,并且是在异步不阻塞的情况下打开该文件。

如果你想知道为什么fs.createReadStream()必须异步地打开文件时怎么能同步,那是因为fs.createReadStream()在返回时还没有打开文件。

在正常使用流的情况下,你可以立即从流中开始读取。但是,在流的内部,如果文件还没有被打开,它将等待文件打开完成后再尝试读取文件。所以,打开文件的过程对流的用户是隐藏的(这通常是一件好事)。

如果你想知道文件什么时候真正被打开,流上有一个open事件,如果打开文件出现错误,则会有一个error事件。所以,技术上来说,fs.readStream()实际上是一个异步操作,异步操作的完成通过openerror事件来通信。

let rstream = fs.createReadStream("temp.txt");
rstream.on('open', (fd) => {
    // file opened now
});
rstream.on('error', (err) => {
    // error on the stream
});

然而,在fs.createReadStream()的正常使用中,程序员不必监视文件打开事件,因为它对用户隐藏,并在下一次读取流时自动处理。当您创建读取流并立即要求从中读取时(这是异步接口),内部流对象会等待文件完成打开,然后从中读取一些字节,等待文件读取完成,然后通知读取操作完成。因此,他们只是将文件打开完成与第一次读取合并在一起,省去了程序员在发出第一个读取操作之前等待文件打开完成的额外步骤。

因此,从技术上讲,fs.createReadStream()是一个具有完成事件的异步操作。但是,由于它已经与读取文件的方式结合在一起,通常不必像异步操作那样使用它,因为它的异步行为已经与异步读取文件的行为结合在了一起。


2
createReadStream 中,文件实际上是什么时候打开的?您提到“稍后”,但是具体是什么时候呢? - jmq
1
@jmq - 如果您查看源代码,您可以看到fs.createReadStream()创建一个ReadStream对象,该对象在构造函数中调用this.open()。但是,这是异步的,因此它会在fs.createReadStream()返回后的某个时间完成。流上的其他方法检查文件是否已经打开,如果没有,则注册事件处理程序以在打开完成时通知它们,并且它们在打开完成后执行其工作。由于所有内容都是异步的,因此这可以工作。 - jfriend00
@jmq - 如果你想知道 open 何时完成,你可以自己在流上注册一个 open 事件的事件监听器。 - jfriend00
重写了这个答案,使其在技术上更加准确,并详细阐述了它在底层是如何工作的。 - jfriend00
child_process.spawnchild_process.fork接受一个长度为3的参数stdio:[],对应于子进程的stdin、stdout和stderr。任何3个值中的可能值都包括流,但如果流尚未open(即已生成open事件),则会抛出错误。因此,在这种情况下,它不会对用户隐藏。 - Craig Hicks
@CraigHicks - 是的,这将是一个特殊情况,您不允许在没有文件描述符的情况下传递流对象。这更多地涉及child_process实现(因为它们将复制fd),而不是一般的流。但是,在那个特定的情况下,您是正确的。 - jfriend00

1

根据nodejs源代码:

  1. fs.createReadStream创建一个ReadStream实例。

  2. ReadStream的_read方法(我们知道每个自定义readablestream必须提供其_read方法)调用fs.read

  3. 我们知道fs.read是异步的(fs.readSync是同步的)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接