Node.js - 事件循環:揭開非同步JavaScript的神秘面紗
你好,未來的編程魔法師們!今天,我們將踏上一段令人興奮的旅程,深入了解Node.js的核心——事件循環(Event Loop)。別擔心如果你之前從未寫過一行代碼;我將成為你進入這個迷人世界的友好導遊。在這個教學結束時,你將會理解Node.js是如何同時處理許多事情,就像你同時應對功課、Netflix和發短信給朋友一樣!
什麼是事件循環?
想像你是一家忙碌餐廳廚房中的廚師。你同時煮著多道菜,計時器滴答作響,訂單不斷湧入。你如何在不燒焦食物或不讓顧客等待的情況下管理一切?這正是事件循環對Node.js所做的!
事件循環就像一位總廚,不斷檢查什麼需要關注,並確保一切運行順暢。它是Node.js能夠進行非阻塞I/O操作的秘訣,儘管JavaScript是單線程的。
關鍵概念
在我們深入探討之前,讓我們熟悉一些關鍵概念:
- 單線程:JavaScript在單一線程上運行,意味著它一次只能做一件事。
- 非阻塞:Node.js可以處理多個操作,而不需要等待每個操作完成才移動到下一個。
- 非同步:任務可以立即開始,稍後完成,這樣其他代碼就可以在這段時間內運行。
事件循環是如何工作的?
讓我們將事件循環分解成容易理解的步驟:
- 在調用堆疊中執行同步代碼
- 檢查計時器(setTimeout, setInterval)
- 檢查懸而未決的I/O操作
- 執行setImmediate回調
- 處理'close'事件
現在,讓我們通過一些代碼示例來看看這是怎麼工作的!
示例 1:同步與非同步代碼
console.log("First");
setTimeout(() => {
console.log("Second");
}, 0);
console.log("Third");
你認為輸出會是什麼?讓我們分析一下:
- "First"會立即被記錄。
- 遇到setTimeout,但Node.js不會等待,而是設定計時器後繼續執行。
- "Third"被記錄。
- 事件循環檢查完成的計時器並執行回調,記錄"Second"。
輸出:
First
Third
Second
驚訝嗎?這展示了Node.js是如何在不阻塞主線程的情況下處理非同步操作的。
示例 2:多個計時器
setTimeout(() => console.log("Timer 1"), 0);
setTimeout(() => console.log("Timer 2"), 0);
setTimeout(() => console.log("Timer 3"), 0);
console.log("Hello from the main thread!");
在這個例子中,我們設定了多個計時器,延遲為0毫秒。然而,事件循環仍然會在主線程結束後處理它們。
輸出:
Hello from the main thread!
Timer 1
Timer 2
Timer 3
事件循環的階段
現在我們已經看到了事件循環的運作,讓我們更詳細地探索它的階段:
1. 計時器階段
這個階段執行由setTimeout()和setInterval()排程的回調。
setTimeout(() => console.log("I'm a timer!"), 100);
setInterval(() => console.log("I repeat every 1 second"), 1000);
2. 懸而未決的回調階段
在這裡,循環執行在下一個循環迭代中延遲的I/O回調。
3. 空閒、預備階段
僅供內部使用。這裡沒有什麼好看的!
4. �輪詢階段
檢索新的I/O事件並執行與I/O相關的回調。
const fs = require('fs');
fs.readFile('example.txt', (err, data) => {
if (err) throw err;
console.log(data);
});
5. 檢查階段
在這裡調用setImmediate()回調。
setImmediate(() => console.log("I'm immediate!"));
6. 關閉回調階段
一些關閉回調,例如socket.on('close', ...),在這裡被處理。
準備一切
讓我們創造一個更複雜的示例,它利用事件循環的不同方面:
const fs = require('fs');
console.log("Start");
setTimeout(() => console.log("Timeout 1"), 0);
setImmediate(() => console.log("Immediate 1"));
fs.readFile('example.txt', (err, data) => {
console.log("File read complete");
setTimeout(() => console.log("Timeout 2"), 0);
setImmediate(() => console.log("Immediate 2"));
});
console.log("End");
執行順序可能會讓你感到驚訝:
- "Start"和"End"會立即被記錄。
- 第一個setTimeout和setImmediate被排隊。
- 開始文件讀取操作。
- 事件循環開始它的周期:
- 第一個setTimeout回調被執行。
- 第一個setImmediate回調被執行。
- 文件讀取完成時,其回調被執行。
- 在文件讀取回調內,另一個setTimeout和setImmediate被排隊。
- 第二個setImmediate在第二個setTimeout之前執行。
常見事件循環方法
以下是Node.js中常見的事件循環相關方法:
方法 | 描述 |
---|---|
setTimeout(callback, delay) | 在延遲毫秒後執行回調 |
setInterval(callback, interval) | 每個間隔毫秒執行回調一次 |
setImmediate(callback) | 在事件循環的下一個迭代中執行回調 |
process.nextTick(callback) | 將回調添加到"下一個滴答隊列"中,在當前操作完成後處理 |
結論
恭喜你!你剛剛踏入了Node.js及其事件循環的迷人世界。記住,就像學騎自行車一樣,掌握非同步編程需要練習。如果它立即沒有點醒你——不要氣餒,繼續嘗試,很快你就能像專家一樣寫出非阻塞代碼!
當我們結束時,這裡有一個有趣的比喻:將事件循環想像成旋轉木馬。不同的任務(如計時器、I/O操作和立即回調)就像試圖跳上木馬的孩子们。事件循環不斷旋轉,按照特定的順序接手和放下任務,確保每個人都能得到一次機會,而且木馬從不停止。
繼續編程,保持好奇心,並記住——在Node.js的世界裡,耐心不僅是一種美德,它是一個回調!
Credits: Image by storyset