我想知道node.js是否保证"过期的执行顺序" (准备执行)通过setTimeout
安排的回调。 manual似乎声称事件循环的定时器阶段具有回调的FIFO队列。
在下面的示例中考虑到这一点,我希望节点安排第一个回调,并在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);
答案 0 :(得分:0)
来自docs
在该精确的毫秒数之后执行的超时间隔不能依赖于执行。这是因为阻塞或保留在事件循环上的其他执行代码将推迟执行超时。唯一的保证是超时不会比声明的超时间隔更早执行。
我认为你的例子是展示所有这些要点,更重要的是可以推迟计时器的事实。在我看来,拨打spawnSync
的呼叫正在推迟定时器,足以导致重叠 - 大概是如果你减少了wait
呼叫在第一个setInterval
中的延迟或增加了上次setInterval
的超时,您会看到更一致的行为。
答案 1 :(得分:0)
仔细检查Node中的回调调度实现后,确实看来发布的示例中second
/ third
回调的顺序确实无法保证。
这就是Node如何在setTimeout
中处理lib/timers.js
回调的原因。简而言之,这些回调存储在按相应延迟时间分组的链表中。现在,如果一个回调触发,Node确定其组,标记当前时间t_now
并处理组内的所有回调。对于每一个,它知道其注册时间t_reg
(通过setTimeout
注册时)和延迟时间delta
。如果t_now - t_reg >= delta
,则调用回调。问题是t_now
仅针对与相同延迟时间相对应的整组回调进行一次评估。
为了说明这一点,我在调试模式(--debug
脚本的./configure
选项)中编译了Node,并将示例执行为:
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
(在{{1中输入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
触发时,Node会处理与T=2067
的延迟时间相关联的整组回调,如上所述,从而进行2000
回调 - 在这种情况下,second
完全等于T-t2
,因此2000
也“幸运地”被解雇了。但是,如果second
回调的调度仅仅second
单位延迟(由于例如在调用1
之前有一些无关的函数调用),setTimeout
回调会更快地触发