iT邦幫忙

2026 iThome 鐵人賽

DAY 3
0
JavaScript

不要再說你不會後端!30 天 Node.js 降維打擊指南 系列 第 3

Day 3:非同步編程的降維打擊(Event Loop 與 async/await)

  • 分享至 

  • xImage
  •  

Node.js 之所以能在效能上痛打傳統後端語言,靠的不是硬體暴力,而是它極度聰明的「任務調度機制」。在傳統後端,遇到讀取大型檔案或資料庫時,程式會「卡住」等待(阻塞),直到資料拿回來才繼續往下走。Node.js 則是完全的「非阻塞(Non-blocking)」。

1. 什麼是 Event Loop(事件迴圈)?

想像一家餐廳。傳統伺服器像是為每一桌客人都聘請一位專屬服務生(多執行緒)。如果客人看菜單看很久,服務生只能乾站在旁邊等,浪費極大的人事與記憶體成本。

Node.js 只有「一位」超級服務生(單執行緒),但他絕對不等待。幫第一桌點完餐送進廚房(發出 I/O 請求)後,立刻轉身去幫第二桌點餐。當廚房把第一桌的菜做好(非同步任務完成),會按鈴通知服務生來端菜(觸發 Event 與 Callback)。

這個不斷巡視客人與廚房的無限迴圈,就是 Event Loop。它讓 Node.js 能用極少的系統資源,同時處理成千上萬個使用者的連線。

2. 演進史:從地獄到降維打擊

過去,為了處理非同步任務完成後的動作,我們必須把函式當作參數傳遞(Callback),這很容易導致一層包一層的「波動拳」程式碼(Callback Hell)。後來出現了 Promise,將非同步任務包裝成一個「承諾」,用語法鏈 .then() 來處理。

最終,現代 JavaScript 迎來了終極降維打擊:async/await
它讓你能用寫同步程式的直覺語法,來執行非同步的邏輯

3. 實作練習:非同步檔案讀寫

我們將使用 Node.js 內建的 fs/promises(檔案系統)模組,來模擬一個耗時的檔案讀取與寫入過程,徹底體會 Event Loop 的運作順序。

步驟 1:建立測試資料
在你的 hello-node 資料夾下,新增一個文字檔 data.txt,裡面隨便寫一行字:

這是一份來自戰錘40K的機檔案。

步驟 2:撰寫非同步邏輯
清空 app.js,將內容換成以下程式碼並存檔:

// 使用內建的 promises 版本模組,原生支援 async/await
import { readFile, writeFile } from 'fs/promises';

// 模擬一個會耗時的「暫停」函式 (利用 Promise 與 setTimeout)
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));

// 在函式前方加上 async,標記內部包含非同步操作
const processData = async () => {
    try {
        console.log("1.  準備讀取檔案...");
        
        // await 會暫停這個函式內部的執行,直到讀取完成
        // 關鍵:這「不會」卡住整個 Node.js 主執行緒
        const content = await readFile('./data.txt', 'utf-8');
        console.log(`2.  讀取成功,內容為:${content}`);

        console.log("3.  模擬資料庫處理中 (強制暫停 2 秒)...");
        await delay(2000); 

        const newContent = content + " \n-> 已被 Node.js 非同步處理完畢!";
        
        console.log("4.  準備寫入新檔案...");
        await writeFile('./output.txt', newContent, 'utf-8');
        
        console.log("5.  檔案寫入完成!請查看資料夾的 output.txt");
    } catch (error) {
        // async/await 最棒的地方在於,可以用傳統的 try...catch 捕捉非同步錯誤
        console.error(" 發生錯誤:", error.message);
    }
};

console.log("--- 程式開始執行 ---");
processData(); // 呼叫非同步函式
console.log("--- 程式主線已跑完,等待非同步任務 ---");

步驟 3:執行與觀察
在終端機執行 node app.js請仔細觀察終端機印出文字的順序與時間差

你會發現順序是這樣的:

  1. --- 程式開始執行 ---
  2. 1. 準備讀取檔案...
  3. --- 程式主線已跑完,等待非同步任務 ---
  4. (底層檔案讀取完畢後) 2. 讀取成功...
  5. 3. 模擬資料庫處理中...
  6. (等待 2 秒後) 4. 準備寫入新檔案...
  7. 5. 檔案寫入完成!
    image
    image

這就是單執行緒不阻塞的完美證明!
當 Node.js 執行到 await readFile 時,它把這個耗時任務丟給作業系統,主執行緒立刻往下跑,印出了最後一行的「程式主線已跑完」。直到作業系統讀完檔案,Event Loop 才會回頭把 async 函式內部剩下的程式碼執行完畢。


上一篇
Day 2:模組化系統的對決(CommonJS vs ES Modules)
系列文
不要再說你不會後端!30 天 Node.js 降維打擊指南 3
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言