我想知道node.js是否保证通过setTimeout
安排的“过期”(准备执行)回调的执行顺序。 manual似乎声称事件循环的计时器阶段有一个 FIFO 回调队列。
在下面的示例中考虑到这一点,我预计 Node 会安排第一个回调,并在 1 秒后按照代码中指定的顺序安排剩余两个回调。现在,当第一个回调触发时,执行“停止”5 秒,这意味着当回调返回时,其他两个回调也准备好执行。
但是,当我运行该示例时,输出似乎是 first, third, second
。奇怪的是,当第二个回调的延迟时间修改为例如2001
时而不是2000
,顺序符合预期,即 first, second, third
。这是设计使然吗?
const spawnSync = require('child_process').spawnSync;
function wait(delta){
spawnSync('sleep', [delta]);
}
setTimeout(() => {
console.log('first');
wait(5);
}, 2000);
wait(1);
setTimeout(() => {
console.log('second');
}, 2000);
setTimeout(() => {
console.log('third');
}, 4000);
最佳答案
仔细检查Node中的回调调度实现,确实看起来second
的顺序是这样的。/third
发布的示例中的回调并不能真正得到保证。
其原因在于 Node 如何处理 setTimeout
lib/timers.js
中的回调。简而言之,这些回调存储在按相应延迟时间分组的链表中。现在,如果触发一个回调,Node 就会确定其组,标记当前时间 t_now
并处理组内的所有回调。对于每一个,它都知道其注册时间 t_reg
(当通过 setTimeout
注册时)和延迟时间 delta
。如果t_now - t_reg >= delta
,它调用回调。问题是t_now
对于与相同延迟时间相对应的整组回调,仅计算一次。
为了说明这一点,我在 Debug模式下编译了 Node(--debug
脚本的 ./configure
选项)并将示例执行为:
NODE_DEBUG=timer node ./example.js
在我的机器上,我得到以下信息:
TIMER 38963: no 2000 list was found in insert, creating a new one
TIMER 38963: no 4000 list was found in insert, creating a new one
TIMER 38963: timeout callback 2000
TIMER 38963: now: 2069
TIMER 38963: _idleStart = 64
first
TIMER 38963: _idleStart = 1074
TIMER 38963: 2000 list wait because diff is 995
TIMER 38963: timeout callback 4000
TIMER 38963: now: 7075
TIMER 38963: _idleStart = 1074
third
TIMER 38963: 4000 list empty
TIMER 38963: timeout callback 2000
TIMER 38963: now: 7076
TIMER 38963: _idleStart = 1074
second
TIMER 38963: 2000 list empty
在这里,我们看到 first
和second
有时会安排回调t1=64
和t2=1074
, 分别。当 first
回调已准备好在时间 T=2069
触发,执行它大约需要 5 秒。一旦这个
执行完成后,Node 在同一组回调中继续(即与相同延迟时间关联的回调),从而检查 second
打回来。但是,它考虑的是当前时间,而不是执行 first
后的时间。回调不过时间T
当它开始处理回调时(在 listOnTimeout
中输入 lib/timers.js
函数)。在我的机器上,自从 second
回调注册时间为 1074
, 2069 - 1074
小于2000
的延迟时间因此 second
回调未执行,而是重新安排为稍后执行,延迟时间为 995
(但是,相对于当前时间,而不是相对于 T
)。
更具体地说,因为 first
回调触发,我们知道T - t1 >= delta
。然而T - t2
之间的关系和delta
不保证。为了说明这一点,如果我删除 wait(1)
打电话,我得到:
TIMER 39048: no 2000 list was found in insert, creating a new one
TIMER 39048: no 4000 list was found in insert, creating a new one
TIMER 39048: timeout callback 2000
TIMER 39048: now: 2067
TIMER 39048: _idleStart = 66
first
TIMER 39048: _idleStart = 67
second
TIMER 39048: 2000 list empty
TIMER 39048: timeout callback 4000
TIMER 39048: now: 7080
TIMER 39048: _idleStart = 67
third
TIMER 39048: 4000 list empty
现在,second
回调安排在t2=67
,即2
晚于 first
的时间单位打回来。现在当first
火灾时间T=2067
, Node 处理与延迟时间2000
相关的整组回调。如上所述,因此进入 second
回调 - 在本例中为 T-t2
正好等于2000
这样second
也“幸运地”被解雇了。然而, second
的调度回调仅延迟 1
单位(例如由于调用 setTimeout
之前进行了一些无关的函数调用), third
回调会更快触发。
关于javascript - Node.js - setTimeout 回调的触发顺序是否与注册的顺序相同?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48614436/