Node.js Callback 非同步回呼函式詳解

在 Node.js 中,幾乎所有的 I/O 操作(例如讀取檔案、資料庫查詢、API 請求)都是非同步的。而 Callback (回呼函式) 是 Node.js 處理非同步運算最基礎、也是最核心的方式。

什麼是 Callback?

簡單來說,Callback 就是「將一個函式作為參數傳遞給另一個函數」,並告知該函數:「當你完成某件特定任務後,請回頭來執行我所傳入的這個函式」。

同步與非同步的回呼

並非所有的 Callback 都是非同步的。

  • 同步 Callback:例如 JavaScript 的 Array.mapArray.forEach
  • 非同步 Callback:例如 Node.js 的 fs.readFile,程式不會等待檔案讀完,而是繼續往下執行。
const fs = require('fs');

console.log('1. 開始讀取檔案');

fs.readFile('data.txt', 'utf8', (err, data) => {
  if (err) {
    console.error('讀取失敗:', err);
    return;
  }
  console.log('3. 檔案內容讀取成功!');
});

console.log('2. 程式繼續執行,不會被阻塞');

Node.js 慣例:Error-First Callback

Node.js 社群達成了一個重要的設計共識,稱為 Error-First Callback(錯誤優先回呼)。你會發現絕大部分的 Node API 在傳入回呼函式時,都遵循以下兩個原則:

  1. 第一個參數是 Error:如果操作發生錯誤,該參數為一個 Error 物件;如果操作成功,則為 null
  2. 後續參數才是資料:例如讀到的字串、資料庫的結果集等。

這種模式強制開發者在處理結果之前,必須先檢查是否有錯誤發生。

回呼地獄 (Callback Hell) 的挑戰

當你需要按順序執行多個非同步操作時,Callback 的巢狀結構會不斷加深,形成所謂的「回呼地獄」或「金字塔形程式碼」:

fs.readFile('user.json', (err, user) => {
  if (err) return handleError(err);

  getUserSettings(user.id, (err, settings) => {
    if (err) return handleError(err);

    updateUserDashboard(settings, (err, result) => {
      if (err) return handleError(err);
      console.log('流程全部完成');
      // 程式碼會一直向右方凹進去,變得極難維護...
    });
  });
});

如何優化?

雖然 Callback 是底層機制,但現代 Node.js 開發通常會搭配以下方式來解決巢狀問題:

  • 具名函式:將匿名函式拆開,獨立定義以提高可讀性。
  • Promise 與 Async/Await:這是目前最主流且優雅的解決方案,我們將在接下來的章節詳細探討。
小撇步:雖然 Callback 現在較少用於複雜的業務邏輯流轉,但理解它對於閱讀舊專案或某些極致效能的底層套件(如 EventEmitter)依然非常重要。