Node.js 的 Event Loop (事件輪詢)到底在做什麼?
這篇文章寫於 2021 年,文中的工具與版本可能已經更新,實作時請以官方文件為準。
本文目錄
因為網路上的教學文我大部分都有看沒有懂,只好自己實作一遍來了解他的運作機制;希望我這篇文章足夠口語化,讓讀者能輕鬆理解,如果有解釋錯誤或是不夠清晰的部分再煩請告知,謝謝。

大綱
一、請用一段話簡述 Node.js 的 Event Loop
二、Node.js 在 Event Loop 邏輯下的執行順序
三、Event Loop 執行的邏輯
➤ 了解 MacroTask 的 6 個階段
➤ NextTick Queue 優先層級最高
➤ MicroTask Queue 優先層級次高
四、解析一開始 Node.js 範例程式執行順序
➤ 順序執行,先把同步函式解決
➤ 進入 Event Loop
一、請用一段話簡述 Node.js 的 Event Loop
「Node.js 之所以高效,是因為採取單執行緒與 Event Loop 的概念;將所有需要等待結果、請求外部資源的函式,全部丟到 Event Loop 中等待;而 Event Loop 的邏輯是 Node.js 底層用 C 語言撰寫的 libuv 庫來運行的。」
有興趣的朋友可以來 GitHub 上看 libuv 的程式:

二、Node.js 在 Event Loop 邏輯下的執行順序
大家可以先用大腦模擬看看下面程式的執行順序:
console.log('start');
process.nextTick(function () {
console.log('nextTick1');
});
setTimeout(function () {
console.log('setTimeout');
}, 0);
new Promise(function (resolve, reject) {
console.log('promise');
resolve('resolve');
}).then(function (result) {
console.log(result);
});
(async function () {
console.log('async');
})();
setImmediate(function () {
console.log('setImmediate');
});
process.nextTick(function () {
console.log('nextTick2');
});
console.log('end');
原始碼:event_loop.js(GitHub Gist)
如果答案跟下面結果一樣,那代表你很了解 Node.js 的 Event Loop 邏輯:
start
promise
async
end
nextTick1
nextTick2
resolve
setTimeout
setImmediate
三、Event Loop 執行的邏輯
了解 MacroTask 的 6 個階段
Timer:等計時器(setTimeout、setInterval)的時間一到,會把他們的 callback 放在這裡等待執行。
Pending callbacks:作業系統層級使用(TCP errors、sockets 連線被拒絕)。
Idle, Prepare:內部使用。
Polling:
- 如果 Queue 不為空,依次取出 callback 函數執行,直到 Queue 為空或是抵達系統最大限制。
- 如果 Queue 為空但有設置「setImmediate」就進入 check 階段。
- 如果 Queue 為空但沒有設置「setImmediate」就會在 Polling 階段等待直到 Queue 有東西或是 Timer 時間抵達。
Check:處理 setImmediate 的 callback。
Close callbacks:處理關閉連線、檔案的 callback。
Macrotask Queue 在 Evevnt Loop 優先層級較低,接下來介紹優先層級更高的兩個 Queue。
NextTick Queue 優先層級最高
「process.nextTick()」的 callback 可以隨意插隊;只要這個 Queue 有東西就會優先執行。
MicroTask Queue 優先層級次高
「Promise 」的狀態從 pending 轉變為 resolve 或 reject 時,此時執行的 callback 會進入這個 Queue。
注意:Promise 在創建時是同步的,不會進入 Event Loop。
四、解析一開始 Node.js 範例程式執行順序
順序執行,先把同步函式解決
- 因為 Promise 在創建時是同步的,所以會先執行
- 在 function 前面加上 async 宣告為異步函式,執行時會被轉換為 Promise
console.log('start');
new Promise(function (resolve, reject) {
console.log('promise');
resolve('resolve');
}).then(function (result) {
console.log(result);
});
(async function () {
console.log('async');
})();
console.log('end');
原始碼:sync_function.js(GitHub Gist)
start
promise
async
end
進入 Event Loop
- 如果執行過程中遇到 NextTick Queue 就優先執行
process.nextTick(function () {
console.log('nextTick1');
});
process.nextTick(function () {
console.log('nextTick2');
});
原始碼:nextTick_queue.js(GitHub Gist)
nextTick1
nextTick2
2. 在處理完最高優先層級後,如果執行過程中遇到 MicroTask Queue 就執行
new Promise(function (resolve, reject) {
console.log('promise');
resolve('resolve');
}).then(function (result) {
console.log(result);
});
原始碼:MicroTask_queue.js(GitHub Gist)
resolve
3. 在前兩個高優先層級的 Queue 清空後,返回 Event Loop 繼續執行,setTimeout 設定的時間已到先執行。
setTimeout(function () {
console.log('setTimeout');
}, 0);
原始碼:setTimeout_function.js(GitHub Gist)
setTimeout
4. 接著再在「Check」 階段執行 setImmediate
setImmediate(function () {
console.log('setImmediate');
});
原始碼:setImmediate_function.js(GitHub Gist)
setImmediate
以上步驟執行完成後就是範例程式的解答啦~希望對大家理解 Event Loop 有幫助。
參考資源:
留言載入中…