Javascript Node.js:同步调用相同延迟的setTimeout回调?

Javascript Node.js:同步调用相同延迟的setTimeout回调?,javascript,node.js,event-loop,Javascript,Node.js,Event Loop,我有以下代码来演示该问题: let count = 5; while (count--) { setTimeout(() => { console.log('timeout'); process.nextTick(() => { console.log('tick'); }); }, 0); } const largeNumber = 20000; for (let i = 0; i <

我有以下代码来演示该问题:

let count = 5;
while (count--) {
    setTimeout(() => {
        console.log('timeout');
        process.nextTick(() => {
            console.log('tick');
        });
    }, 0);
}

const largeNumber = 20000;
for (let i = 0; i < largeNumber; i += 1) {
    for (let j = 0; j < largeNumber; j += 1) {
        // do nothing here, just be sure all the setTimeout callbacks are in the queue when exiting sync code
    }
}
因为事件循环检查
超时
队列,所以它会找到第一个
setTimeout
回调,运行它,然后检查
nextTick
队列。对于进一步的
setTimeout
回调,它也应该这样做

但我得到以下输出:

timeout
timeout
timeout
timeout
timeout
tick
tick
tick
tick
tick

为什么?

setTimeout
nextTick
都会将一个函数放入函数队列中,以便稍后调用

当JavaScript事件循环不忙于执行其他操作时,它将查看该函数队列,以查看是否有任何函数将要运行

当第一个超时函数运行时,它使用
nextTick
将一个函数放在队列的末尾(应尽快运行)

但是,队列上的下一个函数是由
setTimeout
放在那里的下一个函数,它已经到期了,因此它首先运行(依此类推)。

这是由于:

对于
计时器
检查
阶段,对于多个即时事件和计时器,在C到JavaScript之间有一个单一的转换。这种重复数据消除是一种优化形式,可能会产生一些意想不到的副作用

您的代码显示了重复数据消除优化导致的“意外副作用”

事实上,文档中的示例与示例代码非常相似。他们使用的是
setImmediate
而不是
setTimeout
,但概念相同:

当有多个计时器事件在
检查
阶段等待时,
节点
在处理
nextTickQueue
之前处理所有事件

因此,由于所有的
setTimeout
调用都使用了
0
的超时,因此所有回调都会同时在队列中结束,并且由于重复数据消除优化,
节点
处理所有这些调用,从而导致
的“超时”
全部打印5次。一旦所有
setTimeout
回调运行,
节点处理
nextTickQueue
,这将导致所有五个
进程运行。nextTick
回调将导致打印5次
'tick'


请注意,如果引入一个小变量
delay
,使计时器事件不会在同一
检查
阶段的队列中结束,则可以避免重复数据消除优化,并获得预期的输出:

let count = 5;
let delay = 0;
while (count--) {
  setTimeout(() => {
    console.log('timeout');
    process.nextTick(() => {
      console.log('tick');
    });
  }, delay += 1);  // use a tiny variable delay
}

const largeNumber = 20000;
for (let i = 0; i < largeNumber; i += 1) {
  for (let j = 0; j < largeNumber; j += 1) {
    // do nothing here, just be sure all the setTimeout callbacks are in the queue when exiting sync code
  }
}

您可以查看一下,以更好地了解
process.nextTick()
为什么这种行为对您来说是个问题?@Bergi,我不明白它为什么会这样工作。如果我注册了一个
进程.nexTick()
,那么它的回调应该在所有其他
超时之前执行
@bergi我实际上同意OP的说法,即这种行为不会破坏nodejs文档。允许您“调试”nodejs应用程序中的回调堆栈。查看正在运行的代码片段,以便更好地可视化它的执行方式。只需按
Save+Run
进行初始化。根据@AlexandreMiziara发送的文档,它应该在从
timers
queue获取下一次回调之前检查
nextTickQueue
“这是因为process.nextTick()从技术上讲,它不是事件循环的一部分。相反,无论事件循环的当前阶段如何,nextTickQueue都将在当前操作完成后进行处理。“很明显,下一个
setTimeout
已到期,但应在所有超时回调之前执行
nextTick
。@Adam“当事件循环进入给定的阶段时,它将在该阶段的队列中执行回调,直到队列耗尽或执行了最大数量的回调。“建议可以在同一时间调用超时队列中的多个回调phase@bergi但是
进程.nextTick
回调应该在调用堆栈未预料的情况下调用,因此在执行下一次超时回调之前调用。由于答案不正确,敬请投反对票。正如其他人所指出的,
nextTickQueue
通常会在每次
setTimeout
回调后进行处理……但在这种情况下,这不是因为。惊人的答案,这帮助了我解决了问题。非常感谢。
let count = 5;
let delay = 0;
while (count--) {
  setTimeout(() => {
    console.log('timeout');
    process.nextTick(() => {
      console.log('tick');
    });
  }, delay += 1);  // use a tiny variable delay
}

const largeNumber = 20000;
for (let i = 0; i < largeNumber; i += 1) {
  for (let j = 0; j < largeNumber; j += 1) {
    // do nothing here, just be sure all the setTimeout callbacks are in the queue when exiting sync code
  }
}
timeout
tick
timeout
tick
timeout
tick
timeout
tick