在 readline 启动后定义事件处理程序

Defining event handlers after readline initiated

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

"start" reader 似乎没有任何内容。它只是去,而且似乎工作得很好。它怎么知道什么时候开始阅读?它如何保证那些尚不存在的事件将始终选取文件中的每一行?

我一直认为这一切都发生得如此之快,以至于事件附加的速度比从磁盘打开文件并开始读取它的速度还快 - 但事实并非如此。

例如,假设我在创建 lineReader 之后但在附加事件之前放置了一些消耗大量 CPU 的代码。它似乎仍然有效,并且事件仍然为每一行触发。它是如何 "wait" 直到完成重物才开始阅读的?如果我不附加 line 事件,那么无论如何它 运行s 并且 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 特有的 - 似乎是一种常见的节点模式 - 这是最容易定义的 运行。

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

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

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

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

There doesn't seem to be anything to "start" the reader.

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

It just goes, and seems to work perfectly. How does it know when to start reading?

同上

How does it guarantee that those events, which don't exist yet, will always pick up every line in the file?

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

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

For example, suppose I put some heavy CPU-consuming code after the lineReader has been created, but before the events attached. It still seems to work, and the event still fires for each line. How did it "wait" until the heavy stuff was done before it started reading?

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

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

If I don't attach the line event, then it runs anyway and the close event still fires, so it's not like it's waiting for the line event to be created.

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


仅供参考,您可以自己帮助回答这些问题的一种方法是查看 node.js 代码并了解其工作原理。这就是我在这里所做的。这里是 a link to the createInterface() function,您可以在这里看到我在这里描述的内容。

并且,您可以看到 here in the stream doc,其中描述了流开始流动的三种方式,其中一种是附加 data 事件侦听器。