在 之前的文章 中,我們了解到事件循環是 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 佇列。
第一個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() 呼叫。
當呼叫堆疊執行第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()
语句,每个都带有一个回调函数。
为了加快对此可视化的解释,我将省略调用栈讲解。当调用栈执行所有 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中文網其他相關文章!