首頁  >  文章  >  web前端  >  一文帶你了解Node事件循環中的計時器隊列

一文帶你了解Node事件循環中的計時器隊列

青灯夜游
青灯夜游轉載
2023-04-14 15:10:251433瀏覽

一文帶你了解Node事件循環中的計時器隊列

上一篇文章 中,我們探討了微任務佇列及它在各佇列中的優先權順序。在本文,我們將討論計時器佇列,這是 Node.js中用於處理非同步程式碼的另一個佇列。

在深入研究計時器佇列之前,讓我們快速回顧一下微任務佇列。為了將回呼函數排入微任務佇列,我們使用 process.nextTick()Promise.resolve() 等函數。當涉及到執行 Node.js 中的非同步程式碼時,微任務佇列具有最高優先權。 【相關教學推薦:nodejs影片教學程式設計教學

#入隊回呼函數

現在我們到計時器隊列。要將回呼函數排入計時器佇列,我們可以使用 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"));

翻譯:大家不用緊張,這段程式碼就是在上篇「福利實驗」的基礎上,開頭加了3 個setTimeout 語句。

該程式碼包含三個process.nextTick() 的調用,三個Promise.resolve() 的呼叫和三個setTimeout 的呼叫。每個回調函數記錄適當的訊息。所有三個setTimeout 呼叫都有0ms 的延遲,這表示在執行每個setTimeout 語句時,回呼函數都會立即入隊到計時器佇列等待。第二次process.nextTick() 和第二次Promise.resolve() 都有額外的process.nextTick() 語句,並且每個都帶有一個回調函數。

視覺化

一文帶你了解Node事件循環中的計時器隊列

當呼叫堆疊執行所有語句後,在nextTick 佇列中有3 個回調,在Promise 佇列中有3 個回調,在計時器佇列中也有3 個回呼。沒有程式碼要執行,控制權進入事件循環。

nextTick 佇列具有最高優先權,其次是 Promise 佇列,然後是計時器佇列。從 nextTick 佇列中取得第1 個回呼並執行它,將一則訊息記錄到控制台。接著取得第 2 個回呼並執行它,這也會記錄一則訊息。第 2 個回調包含 process.nextTick() 的調用,該方法將新的回調添加到了 nextTick 隊列中。繼續執行,並取得和執行第 3 個回呼以及記錄一則訊息。最後,我們將新加入 nextTick 佇列的回呼函數取出並在呼叫堆疊中執行,從而在控制台上輸出了第四個日誌資訊。

當 nextTick 佇列為空時,事件循環轉向 Promise 佇列。從佇列中取得第 1 個回調,在控制台列印一條訊息,第二個回呼效果類似,並且也為 nextTick 佇列新增了一個回呼。 Promise 中的第 3 個回呼被執行,接著日誌訊息被輸出。此時 Promise 佇列已空,事件循環檢查 nextTick 佇列是否有新的回調,找到之後同樣將訊息記錄到控制台。

現在,兩個微任務佇列都空了,事件循環轉向計時器佇列。我們有三個回調,每個回呼依序從計時器佇列中取出並在呼叫堆疊上執行,將分別列印 "setTimeout 1"、"setTimeout 2" 和 "setTimeout 3"。

this is process.nextTick 1
this is process.nextTick 2
this is process.nextTick 3
this is the inner next tick inside next tick
this is Promise.resolve 1
this is Promise.resolve 2
this is Promise.resolve 3
this is the inner next tick inside Promise then block
this is setTimeout 1
this is setTimeout 2
this is 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() 的呼叫。

視覺化

assets_YJIGb4i01jvw0SRdL5Bt_c4034ba006d840128b729005183abdf4_compressed (1).gif

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

在执行计时器队列中的每个回调后,事件循环会返回检查微任务队列。检查 nextTick 队列确定需要执行的回调函数。这时第二个 setTimeout 推入的回调函数出队并在调用栈上执行,结果“inner nextTick”消息打印到控制台。

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

this is process.nextTick 1
this is process.nextTick 2
this is process.nextTick 3
this is the inner next tick inside next tick
this is Promise.resolve 1
this is Promise.resolve 2
this is Promise.resolve 3
this is the inner next tick inside Promise then block
this is setTimeout 1
this is setTimeout 2
this is inner nextTick inside setTimeout
this is 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”。

this is setTimeout 3
this is setTimeout 2
this is setTimeout 1

推论

计时器队列回调按照先进先出(FIFO)的顺序执行。

总结

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

原文链接:Visualizing The Timer Queue in Node.js Event Loop,2023年4月4日,by Vishwas Gopinath

更多node相关知识,请访问:nodejs 教程

以上是一文帶你了解Node事件循環中的計時器隊列的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:juejin.cn。如有侵權,請聯絡admin@php.cn刪除