likes
comments
collection
share

(三)Node.js事件循环中的定时器队列可视化

作者站长头像
站长
· 阅读数 54

(三)Node.js事件循环中的定时器队列可视化

原文:Visualizing The Timer Queue in Node.js Event Loop

作者:VISHWAS GOPINATH

欢迎阅读我们关于可视化 Node.js 事件循环系列的第三篇文章。在上一篇文章中,我们探讨了微任务队列及其在执行异步代码时的优先级顺序。在本文中,我们将讨论定时器队列( Timer Queue),这是 Node.js 中用于处理异步代码的另一个队列。

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

回调函数排序

现在让我们继续讨论定时器队列。要将回调函数排队到定时器队列中,我们可以使用setTimeoutsetInterval。出于这篇博文的目的,我们将使用setTimeout.

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

实验三

// 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的调用。每个回调函数记录一条适当的消息。这三个setTimeout调用的延迟均为0ms,这意味着一旦setTimeout在调用堆栈上执行每个语句,回调函数就会入队。第二个process.nextTick(),第二个Promise.resolve()有一个额外的process.nextTick()语句,每个都有一个回调函数。

可视化

(三)Node.js事件循环中的定时器队列可视化

当调用堆栈执行所有语句时,我们最终在 nextTick 队列中有三个回调,在 Promise 队列中有三个,在 定时器队列中有三个。在没有进一步代码需要执行的情况下,进入事件循环。

在这三个队列中,nextTick 队列具有最高的优先级,其次是 Promise 队列,最后是 定时器队列。首先,从 nextTick 队列中取出第一个回调并执行,同时在控制台上记录一条消息。然后,取出第二个回调并执行,同时也记录了一条消息。第二个回调包含对process.nextTick()的调用,这会向 nextTick 队列中添加一个新的回调。执行继续,取出第三个回调并执行,同时也记录了一条消息。最后,新添加的回调被取出并在调用堆栈上执行,从而在控制台中产生第四条日志消息。

在 nextTick 队列为空后,事件循环进入 Promise 队列。首先,取出第一个回调并在调用堆栈上执行,这将在控制台中打印一条消息。第二个回调具有类似的效果,也会向 nextTick 队列添加一个回调。接着,Promise 中的第三个回调被执行,产生下一条日志消息。此时,Promise 队列为空,事件循环会检查 nextTick 队列是否有新的回调。它会发现一个新的回调,也是通过向控制台记录一条消息来执行的。

现在,两个 Microtask 队列都为空,事件循环移至定时器队列。我们有三个回调,它们中的每一个都在调用堆栈上一个一个地出队和执行。这将打印“setTimeout 1”、“setTimeout 2”和“setTimeout 3”。

推论

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

好了,至此,优先级顺序是nextTick队列,其次是Promise队列,最后是定时器队列。现在让我们进行下一个实验。

实验四

// 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().

可视化

(三)Node.js事件循环中的定时器队列可视化

让我们应用我们从之前的实验中学到的知识,并快进到微任务队列中的回调已经被执行的位置。假设我们有三个回调在定时器队列中排队。第一个回调出列并在调用堆栈上执行,导致“setTimeout 1”消息打印到控制台。事件循环继续并运行第二个回调,导致“setTimeout 2”消息被打印到控制台。但是,这也会在 nextTick 队列中排队一个回调函数。

在计时器队列中执行每个回调后,事件循环返回并检查微任务队列。它检查 nextTick 队列并确定需要执行的回调。此回调出队并在调用堆栈上执行,导致“inner nextTick”消息打印到控制台。

现在微任务队列为空,控制权返回到计时器队列,执行最后一个回调,控制台中出现“setTimeout 3”消息。

推论

微任务队列中的回调在定时器队列中的回调执行之间执行

实验五

// index.js
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有 1000 毫秒的延迟,第二个有 500 毫秒的延迟,第三个有 0 毫秒的延迟。回调函数在执行时只是将一条消息记录到控制台。

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

推论

定时器队列回调以先进先出 (FIFO) 的顺序执行。

结论

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

继续阅读

第1部分:(一)可视化理解 Node.js 事件循环完整指南

第2部分:(二)Node.js事件循环中nextTick和Promise队列可视化

第3部分:(三)Node.js事件循环中的定时器队列可视化

第4部分:(四)Node.js事件循环中的I/O队列可视化

转载自:https://juejin.cn/post/7225596319447105595
评论
请登录