首頁 >web前端 >js教程 >深入淺析Node事件循環中的微任務佇列

深入淺析Node事件循環中的微任務佇列

青灯夜游
青灯夜游轉載
2023-04-13 17:39:151847瀏覽

深入淺析Node事件循環中的微任務佇列

之前的文章 中,我們了解到事件循環是 Node.js 的關鍵部分,用於協調同步和非同步程式碼的執行。

它由六個不同的隊列組成。一個 nextTick 佇列和一個 Promise 佇列(被稱為微任務佇列)、一個計時器佇列、一個 I/O 佇列、一個檢查佇列,最後是關閉佇列。 【相關教學推薦:nodejs影片教學程式教學

#在每次循環中,回呼函數會在適當時出隊(dequeued)並在調用棧上執行。從本文開始,我們會進行一些實驗以確保我們對事件循環的理解是正確的。

我們的第一組實驗,將重點放在 nextTick 隊列 和 Promise 隊列。但在深入實驗之前,需要先了解如何將回呼函數排到隊列中。

注意:所有實驗都是使用 CommonJS 模組格式進行的。

入隊回呼函數

要將回呼函數加入 nextTick 佇列中,我們可以使用內建的 process.nextTick() 方法。語法很簡單:process.nextTick(callbackFn)。當方法在呼叫堆疊上執行時,回呼函數將被加入到 nextTick 佇列中。

要將回呼函數加入 Promise 佇列中,我們需要使用 Promise.resolve().then(callbackFn)。當 Promise 解決時(resolve),傳遞給 then() 的回呼函數將會被入隊到 Promise 佇列中。

現在我們已經了解如何在兩個佇列中加入回呼函數,讓我們開始第一個實驗吧。

實驗一

程式碼

// index.js
console.log("console.log 1");
process.nextTick(() => console.log("this is process.nextTick 1"));
console.log("console.log 2");

這段程式碼,記錄了三個不同的語句。第二個語句使用 process.nextTick() 將回呼函數排入 nextTick 佇列。

視覺化

深入淺析Node事件循環中的微任務佇列

第一個console.log() 語句被推入呼叫堆疊中執行。它在控制台中記錄相應的訊息,然後彈出堆疊。

接下來,在呼叫堆疊上執行 process.nextTick(),將回呼函數入隊到 nextTick 佇列,並彈出。此時仍有使用者編寫的程式碼需要執行,因此回呼函數必須等待其輪到。

執行繼續進行,最後一個 console.log() 語句被推入堆疊中,該訊息記錄在控制台中,並將該函數從呼叫堆疊中彈出。現在,沒有更多的使用者編寫同步程式碼需要執行,因此控制權進入事件循環。

nextTick 佇列中的回呼函數被推入呼叫棧,console.log() 被推入呼叫堆疊並且執行,在控制台記錄對應的訊息。

console.log 1
console.log 2
this is process.nextTick 1

推論

所有使用者所寫的同步 JavaScript 程式碼優先於非同步程式碼執行。

讓我們繼續進行第二個實驗。

實驗二

程式碼

// index.js
Promise.resolve().then(() => console.log("this is Promise.resolve 1"));
process.nextTick(() => console.log("this is process.nextTick 1"));

我們有一個 Promise.resolve().then() 呼叫和一個 process.nextTick() 呼叫。

視覺化

assets_YJIGb4i01jvw0SRdL5Bt_b32c606155c14ecfa60c8dc102f3bcf0_compressed (1).gif

當呼叫堆疊執行第1 行時,它將回調函數排隊到Promise 佇列中;當呼叫棧執行第2 行時,它將回呼函數排隊到nextTick 佇列中。

第 2 行後沒有程式碼需要執行。

控制權進入事件循環,在其中 nextTick 佇列優先於 Promise 佇列(這是 Node.js 運行時的工作方式)。

事件循環執行 nextTick 佇列回呼函數,然後再執行 Promise 佇列回呼函數。

控制台顯示“this is process.nextTick 1”,然後是“this is Promise.resolve 1”。

this is process.nextTick 1
this is Promise.resolve 1

推論

nextTick 佇列中的所有回呼函數優先於 Promise 佇列中的回呼函數執行。

讓我帶你走一遍上述第二個實驗的更詳細版本。

福利實驗

#
// index.js
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() 呼叫語句。每個回調函數記錄適當的訊息。

但是,第二个 process.nextTick() 和第二个 Promise.resolve() 都有一个额外的 process.nextTick() 语句,每个都带有一个回调函数。

可视化

assets_YJIGb4i01jvw0SRdL5Bt_b32c606155c14ecfa60c8dc102f3bcf0_compressed (2).gif

为了加快对此可视化的解释,我将省略调用栈讲解。当调用栈执行所有 6 个语句时,nextTick 队列中有 3 个回调函数,Promise 队列中也有 3 个回调函数。没有其他要执行的内容后,控制权进入事件循环。

我们知道,nextTick 队列中具有高优先级。首先执行第 1 个回调函数,并将相应的消息记录到控制台中。

接下来,执行第 2 个回调函数,记录了第 2 条日志语句。但是,这个回调函数包含另一个对 process.nextTick() 的调用,该方法内的回调函数(译注:即() => console.log("this is the inner next tick inside next tick"))入队到nextTick 队列末尾。

然后 Node.js 执行第 3 个回调函数并将相应消息记录到控制台上。最初只有 3 个回调,但是因为第 2 次时向nextTick 队列又添加了一个,所以变成 4 个了。

事件循环将第 4 个回调函数推入调用栈,执行 console.log() 语句。

接着处理完 nextTick 队列之后,控制流程进入到 Promise 队列。Promise 队列与 nextTick 队列处理方式类似。

首先记录“Promise.resolve 1”,然后是“Promise.resolve 2”,这时因为调用 process.nextTick() 的原因,一个函数(译注:即 () => console.log("this is the inner next tick inside Promise then block") ) 被推入 nextTick 队列了。尽管如此,控制流程仍停留在 Promise 队列,因此还会继续执行队列中的其他函数。然后我们得到“Promise.resolve 3”,此时 Promise 队列为空。

Node.js 将再次检查微任务队列中是否有新的回调。由于 nextTick 队列中有一个回调,因此会执行这个回调,导致我们的最后一条日志输出。

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

这可能是一个稍微高级的实验,但推论仍然相同。

推论

nextTick 队列中的所有回调函数优先于 Promise 队列中的回调函数执行。

使用 process.nextTick() 时要小心。过度使用此方法可能会导致事件循环饥饿,从而阻止队列中的其余部分运行。当存在大量的 nextTick() 调用时,I/O 队列是无法执行自己的回调函数的。官方文档建议使用 process.nextTick() 的两个主要场景:处理错误或在调用栈为空事件循环继续之前执行回调用。所以在使用 process.nextTick() 时,要明智一些。

总结

实验表明,用户编写的所有同步 JavaScript 代码优先于异步代码执行,并且 nextTick 队列中的所有回调函数优先于 Promise 队列中的回调函数执行。

原文链接:Visualizing nextTick and Promise Queues in Node.js Event Loop,2023年3月30日,by Vishwas Gopinath

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

以上是深入淺析Node事件循環中的微任務佇列的詳細內容。更多資訊請關注PHP中文網其他相關文章!

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