小 node.js 示例中的回调队列顺序

Callback queue order in small node.js example

"use strict"

const
    fs = require('fs'),
    stream = fs.createReadStream("file.txt"),
    timeout = 0;

stream.on('data', function() {
    console.log("File Read");
});

setTimeout(function(){
    console.log("setTimeout : " + timeout);
}, timeout);

我正在学习 node.js / javascript 我想知道为什么这个程序 returning

setTimeout : 0
File Read

而不是相反。

如果我错了请纠正我,但在 javascript 中回调队列是 fifo,先堆叠 stream,然后再堆叠?

我认为由于 nodejs 的非阻塞性质,两个回调在 "parallel" 中都是 运行 并且 setTimeout 首先完成并且 return (例如,1000 毫秒的超时会切换结果。)

Correct me if I'm wrong but in javascript the callback queue is fifo, stacking the stream first and out first?

不完全是。异步回调按照它们 完成 它们的操作 的顺序处理 FIFO,而不是按照它们的操作开始的顺序。因此,完成操作所花费的时间对于确定何时安排回调非常重要。短操作可能会在长操作之前完成,即使它们是稍后开始的。

打开文件并开始读取它需要时间,而 setTimeout() 不需要时间,所以它先发生。当您有独立的异步操作时,您几乎永远无法 "know" 它们的发生顺序,因为它取决于各种函数的内部时序。

readStream 操作在计时器启动之前启动,但由于两个异步操作内部所需的工作量,计时器在 readStream 操作获取其第一个数据之前完成。

I think that because of the non-blocking nature of nodejs, both callback are run in "parallel" and setTimeout finishes first and return (for example a timeout of 1000ms would switch the results.)

是的,这是正确的。


这样想。你有两个强大的扩音器和一个非常好的麦克风。您设置了两个目标以将扩音器指向并收听返回的回声。一个目标很近,一个目标很远。你先用扩音器对着很远的目标开火,然后立即用扩音器对着近处的目标开火。毫不奇怪,即使您在第一个目标之后发送爆炸,您也会先收到来自近处目标的回波,这仅仅是因为来自远处目标的回波需要更长的时间才能穿越所有额外的距离并返回给您。你的readStream也是如此。即使您先启动它,它也比 setTimeout(fn, 0) 花费的时间长得多,因此 setTimeout() 首先完成,因此首先调用它的回调。


如果时机对您很重要,那么您应该使用诸如 promises 之类的工具来专门对您的异步操作进行排序,或者等到所有必要的结果都准备就绪。一个好的设计不应该 "assume" 一个异步操作先于另一个异步操作完成,除非您的代码通过对操作排序来特别保证这一点。

令人惊讶的是,我在 2 天前做了一个解释这个概念的视频教程,而你问了这个问题。 Do watch this 9 min video to get a good understanding of this.这里是解释。在 Nodejs 中,在一般的 JS 中,有一个概念叫做 Eventloop。 Eventloop 的作用是监视 运行 和堆栈中的代码。正如您所说,从代码块推入堆栈的是 FIFO。但是,如果必须执行任何异步或回调方法或操作,则 Eventloop 会立即采取行动来处理这些。 eventloop 本质上做了什么 8s 它有一个自己的队列,它维护这些回调方法。当栈空闲时,将这段从队列中取出的代码入栈执行。

This is purely because of node js's IO non blocking feature. Your file read operation is an IO thing hence it will be pushed to event queue and the setTimeOut function will be executed immediately. Once the file read is done, using its callback it will join the main control flow. Hence setTimeOut will be executed first and then the File read. Simple.