首页 > 解决方案 > 在 readline 启动后定义事件处理程序

问题描述

在 NodeJS 中逐行读取文件的教科书方法似乎是调用readline.createInterface,然后为line和附加事件处理程序close

似乎没有任何东西可以“启动”读者。它只是去,似乎完美地工作。它如何知道何时开始阅读?它如何保证那些尚不存在的事件总是会占用文件中的每一行?

我一直认为这一切都发生得如此之快,以至于事件的附加速度比从磁盘打开文件并开始读取它的速度要快——但这并没有真正成立。

例如,假设我在创建 lineReader 之后,但在附加事件之前放置了一些占用大量 CPU 的代码。它似乎仍然有效,并且事件仍然为每一行触发。它是如何“等到”繁重的工作完成后才开始阅读的?如果我不附加line事件,那么它无论如何都会运行并且close事件仍然会触发,所以它不像是在等待line事件被创建。

var lineReader = readline.createInterface({
    input: fs.createReadStream("input.txt")
});

// EVENTS HAVE NOT BEEN CREATED YET

lineReader.on("line", line => { console.log(line); });
lineReader.on("close", () => { console.log("DONE"); });

这不是特定于lineReader- 似乎是一种常见的节点模式 - 这只是最容易定义和运行的。

标签: node.jseventsfs

解决方案


在内部,readline.createInterface()正在创建一个流。默认情况下,流是暂停的。它们以多种方式取消暂停,这里相关的data是添加事件侦听器时。

并且,在 内部readline.createInterface(),添加了一个data事件处理程序。这开始流流动,它将开始发出datareadline 代码将解析为 line 事件的事件。

此外,由于 node.js 和流是事件驱动的,并且 node.js 将您的 Javascript 作为单线程运行,这意味着在您的设置代码完成执行之前不会发生任何事件。在内部,node.js 可能已经开始读取文件(在内部使用异步 I/O 和线程),但即使它在您的设置代码完成执行之前完成了对文件的第一次读取,它所做的只是在其中插入一个data事件事件队列。data在您的设置代码执行完毕并将控制权返回给 node.js 事件循环之前,node.js 不会处理该事件。

然后,data将调用事件回调,readline 代码将解析来自第一个事件的数据,如果第一个数据事件中有一个完整的行,它将触发一个line事件。

似乎没有任何东西可以“启动”读者。

在 readStream 上附加一个data事件处理程序(在 readline 代码内部)是告诉流开始流动的原因。

它只是去,似乎完美地工作。它如何知道何时开始阅读?

和上面一样。

它如何保证那些尚不存在的事件总是会占用文件中的每一行?

readline 代码在其data事件处理程序中从文件接收原始数据。然后它将该代码解析为行并line为它找到的每一行发出事件。当文件读取跨越行边界时,它必须缓冲部分行并等待该行的其余部分出现在data流中的下一个事件上。

当 linereader 代码看到流已完成读取并且没有更多字节时,它会发送最后一行(如果缓冲区中有一行),然后发出close事件告诉侦听器它已完成。

例如,假设我在创建 lineReader 之后,但在附加事件之前放置了一些占用大量 CPU 的代码。它似乎仍然有效,并且事件仍然为每一行触发。它是如何“等到”繁重的工作完成后才开始阅读的?

这是因为 node.js 是事件驱动的。流中的第一个data事件(readline 代码内部)是fs.readFile()函数的结果,它通过事件队列通知完成。直到当前的 Javascript 完成并将控制权返回给事件循环(此时它将为事件队列中等待的下一个事件提供服务),才会处理事件队列中的事件。因此,无论在附加事件处理程序之前有多少消耗 CPU 的代码,readline 的内部都不会被告知从文件中读取的第一个数据,直到所有这些都完成。

正是这种单线程、事件驱动的特性确保您可以在触发这些事件之前安装事件侦听器,这样您就不会错过它们。

如果我不附加 line 事件,那么它无论如何都会运行并且 close 事件仍然会触发,所以它不像是在等待 line 事件被创建。

正确的。无论您是否有事件侦听器, readline 代码都会data在调用中附加事件处理程序。因此,流将开始流动,并且无论您是否有事件处理程序,文件都会被读取。createInterface()lineline


仅供参考,您可以自己帮助回答这些问题的一种方法是查看 node.js 代码,看看它是如何工作的。这就是我在这里所做的。这是该功能的链接createInterface(),您可以在其中看到我在此处描述的内容。

而且,您可以在流文档中看到这里,其中描述了流开始流动的三种方式,其中一种是附加data事件侦听器。


推荐阅读