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

Callback queue order in small node.js example

本文关键字:node js 示例 队列 顺序 回调      更新时间:2023-09-26
"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,我想知道为什么这个程序返回

setTimeout : 0
File Read

而不是相反

纠正我,如果我错了,但在javascript回调队列是fifo,首先堆叠stream和先出?

我认为,由于nodejs的非阻塞性质,两个回调都是"并行"运行的,setTimeout首先完成并返回(例如,1000ms的超时将切换结果)

纠正我,如果我错了,但在javascript回调队列是fifo,先堆叠流,先输出?

不完全是。异步回调是按照完成操作的顺序进行FIFO处理的,而不是按照操作开始的顺序。因此,操作完成所需的时间量对于确定何时调度回调非常重要。短操作可以在长操作之前完成,即使它们较晚开始。

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

readStream操作在计时器开始之前开始,但计时器在readStream操作获得第一个数据之前完成,这只是因为两个异步操作在内部需要大量的工作。

我认为,由于nodejs的非阻塞性质,两者回调是并行运行的,setTimeout先完成,然后返回(例如,1000ms的超时将切换结果)

对,这是正确的。


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


如果时间对你来说很重要,那么你应该使用像promises这样的工具来明确地排序你的异步操作,或者等到所有必要的结果都准备好了。一个好的设计不应该"假设"一个异步操作将在另一个操作之前完成,除非你的代码通过排序操作来明确保证这一点。

令人惊讶的是,我在两天前做了一个视频教程来解释这个概念,而你却问了这个问题。请观看这段9分钟的视频,以便更好地理解这一点。下面是解释。在Nodejs和JS中有一个叫做Eventloop的概念。Eventloop所做的是监视正在运行和堆栈上的代码。正如你所说的,从代码块推送到堆栈上的是FIFO。但是,如果有任何异步或回调方法或必须执行的动作,那么Eventloop将跳转到动作中来处理这些。eventloop本质上做的是它有一个自己的队列,它在那里维护这些回调方法。当堆栈空闲时,这段来自queue的代码被放到堆栈中并执行。

这纯粹是因为node js的IO非阻塞特性。你的文件读取操作是一个IO的事情,因此它将被推到事件队列和setTimeOut函数将立即执行。一旦文件读取完成,使用它的回调,它将加入主控制流。因此首先执行setTimeOut,然后读取File。简单。