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

时间:2018-04-13 12:04:43

标签: 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 < 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

因为事件循环检查timeouts队列,它会找到第一个setTimeout回调,运行它,然后检查nextTick队列。对于进一步的setTimeout回调,它应该做同样的事情。

但我得到以下输出:

timeout
timeout
timeout
timeout
timeout
tick
tick
tick
tick
tick

为什么?

2 个答案:

答案 0 :(得分:2)

setTimeoutnextTick会在函数队列中放置一个函数,以便稍后调用。

当JavaScript事件循环不忙于执行其他操作时,它会查看该功能队列以查看是否有任何操作。

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

但是,队列中的下一个函数是由setTimeout放置的下一个函数,它是已到期,因此它首先运行(依此类推)。

答案 1 :(得分:0)

这是由于Deduplication

  

对于time taken: 92.121481ms timers阶段,在C到JavaScript之间有一个单一转换,用于多个立即数和计时器。这种重复数据删除是一种优化形式,可能会产生一些意外的副作用。

您的代码显示出由重复数据删除优化引起的“意外副作用”。

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

setTimeout阶段有多个计时器事件等待时,check在处理Node之前先处理所有所有

因此,由于所有nextTickQueue调用都使用setTimeout超时,所有回调都同时在队列中结束,并且由于重复数据删除优化,0处理< strong>全部,这会导致Node被全部打印5次。一旦所有'timeout'回调都运行,setTimeout将处理Node,这将导致所有五个nextTickQueue回调都运行,从而导致process.nextTick被打印5次。 / p>


请注意,如果您引入一个微小的变量'tick',以使计时器事件不会在同一delay阶段中出现在队列中,那么您将避免重复数据删除优化,并获得与以前相同的输出。期待:

check

输出:

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
  }
}