理解Node.js中Event Loop(三)

理解Node.js中Event Loop(三)

本文翻译至:Visualizing The Timer Queue in Node.js Event Loop

在深入了解Timer(计时器)队列之前,让我们快速回顾一下微任务队列。要将回调函数排队到微任务队列中,我们使用 process.nextTick()Promise.resolve() 等函数。在 Node.js 中执行异步代码时,微任务队列具有最高优先级

目录

添加回调函数到队列

现在让我们继续讨论计时器队列。要将回调函数排队到计时器队列中,我们可以使用诸如 setTimeout 和 setInterval 之类的函数。出于本博文的目的,我们将使用 setTimeout 。

为了理解Timer Queue中的执行顺序,我们进行一系列的实验。我们将在微任务队列和定时器队列中对任务进行排列。

实验3

index.js
setTimeout(() => console.log("this is setTimeout 1"), 0);
setTimeout(() => console.log("this is setTimeout 2"), 0);
setTimeout(() => console.log("this is setTimeout 3"), 0);

process.nextTick(() => console.log("this is process.nextTick 1"));
process.nextTick(() => {
  console.log("this is process.nextTick 2");
  process.nextTick(() =>
    console.log("this is the inner next tick inside next tick")
  );
});
process.nextTick(() => console.log("this is process.nextTick 3"));

Promise.resolve().then(() => console.log("this is Promise.resolve 1"));
Promise.resolve().then(() => {
  console.log("this is Promise.resolve 2");
  process.nextTick(() =>
    console.log("this is the inner next tick inside Promise then block")
  );
});
Promise.resolve().then(() => console.log("this is Promise.resolve 3"));

该代码包含对 process.nextTick() 的三次调用、对 Promise.resolve() 的三次调用以及对 setTimeout 的三次调用。

当执行栈执行所有语句时,我们最终会在 nextTick 队列中添加三个回调函数,在 Promise 队列中添加三个回调函数,在 Timer 队列中添加三个回调函数。之后主线程没有代码要执行,控制权交给事件循环。

nextTick队列的优先级最高,其次是Promise队列,最后是Timer队列。

  1. 首先,nextTick 队列中的第一个回调函数将出列并执行,并将消息log到控制台。然后,第二个回调函数出队并执行,它也会记录一条消息。第二个回调包括对 process.nextTick() 的调用,它又将新的回调添加到 nextTick 队列中。执行继续,第三个回调函数出队列并执行,同时log一条消息。最后,新添加的回调函数出队并在执行栈上执行,从而在控制台中显示第四条日志消息。

  2. nextTick队列为空后,事件循环进入Promise队列。第一个回调函数出队列并在执行栈上执行,在控制台中打印一条消息。第二个回调具有类似的效果,并且向 nextTick 队列添加了一个回调函数。 接着Promise 中的第三个回调被执行,打印一条日志消息。此时,Promise 队列为空,事件循环检查 nextTick 队列中是否有新的回调函数。它找到一个,并将其弹出队列执行。

  3. 现在,两个微任务队列都是空的,事件循环移至Timer队列。我们有三个回调,每个回调都被移出队列并在调用堆栈上一一执行。这将打印“setTimeout 1”、“setTimeout 2”和“setTimeout 3”。

由实验3推断

微任务队列中的回调在计时器队列中的回调之前执行。

好了,到目前为止,优先级顺序是nextTick队列其次是Promise队列最后是timer队列。现在让我们进行下一个实验。

实验4

index.js
setTimeout(() => console.log("this is setTimeout 1"), 0);
setTimeout(() => {
  console.log("this is setTimeout 2");
  process.nextTick(() =>
    console.log("this is inner nextTick inside setTimeout")
  );
}, 0);
setTimeout(() => console.log("this is setTimeout 3"), 0);

process.nextTick(() => console.log("this is process.nextTick 1"));
process.nextTick(() => {
  console.log("this is process.nextTick 2");
  process.nextTick(() =>
    console.log("this is the inner next tick inside next tick")
  );
});
process.nextTick(() => console.log("this is process.nextTick 3"));

Promise.resolve().then(() => console.log("this is Promise.resolve 1"));
Promise.resolve().then(() => {
  console.log("this is Promise.resolve 2");
  process.nextTick(() =>
    console.log("this is the inner next tick inside Promise then block")
  );
});
Promise.resolve().then(() => console.log("this is Promise.resolve 3"));

实验四的代码与实验三的代码基本相同,但有一处不同。传递给第二个 setTimeout 函数的回调函数现在包含了一个 process.nextTick() 的调用。

可视化:

让我们快进到第一轮微任务队列中的回调已经执行完毕的时间后。假设此时Timer队列中有三个回调。第一个回调被弹出并在调用栈上执行,控制台打印出一条 "this is setTimeout 1" 消息。接着事件循环继续执行第二个回调,向控制台打印一条 "this is setTimeout 2" 信息。不过,这同时也在 nextTick 队列中添加了一个回调函数。

Timer队列中的每个回调执行完毕后,事件循环都会重新检查微任务队列,是否有新添加的微任务。在这里就是刚才nextTick添加的回调函数,当确认微任务队列有需要的执行的回调任务后,此回调在执行栈上弹出并执行,从而将 “this is inner nextTick inside setTimeout” 消息打印到控制台。

此时微任务队列为空,事件循环继续检查Timer队列,发现并执行队列内的最后一个回调,在控制台中显示 “this is setTimeout 3” 消息。

由实验4推断

微任务队列中的回调在Timer队列中的回调执行之间执行

实验5

setTimeout(() => console.log("this is setTimeout 1"), 1000);
setTimeout(() => console.log("this is setTimeout 2"), 500);
setTimeout(() => console.log("this is setTimeout 3"), 0);

该代码包含三个 setTimeout 语句,分别按照不同延迟执行三个不同的回调函数。第一个 setTimeout 延迟为 1000ms,第二个延迟为 500ms,第三个延迟为 0ms。

我们将跳过此实验的可视化,因为代码片段的执行非常简单。当进行多个 setTimeout 调用时,事件循环首先将延迟最短的回调函数排队,然后在其他调用之前执行它。结果,我们观察到“setTimeout 3”首先执行,然后是“setTimeout 2”,然后是“setTimeout 1”。

由实验5推断

Timer队列回调按先进先出 (FIFO) 顺序执行。


结论

实验表明,微任务队列中的回调比定时器队列中的回调具有更高的优先级,微任务队列中的回调会在定时器队列中的回调执行之间被执行。定时器队列遵循先进先出(FIFO)顺序。


此系列文章:

理解Node.js中Event Loop(一)

理解Node.js中Event Loop(二)

理解Node.js中Event Loop(三)

理解Node.js中Event Loop(四)

理解Node.js中Event Loop(五)

理解Node.js中Event Loop(六)

理解Node.js中Event Loop(七)