Node.js Promise 非同步控制流詳解

為了解決傳統 Callback 巢狀過深 (Callback Hell) 導致的維護困境,JavaScript 在 ES6 中引入了 Promise。它是一個物件,代表了一個「目前尚未完成,但承諾在未來某個時間點會給出結果」的操作。

Promise 的三種生命週期狀態

理解 Promise 的運作,首先要明白它在執行過程中會切換的三種狀態:

  1. Pending (進行中):初始狀態,非同步操作還在跑,尚未有結果。
  2. Fulfilled (已實現):操作成功完成。Promise 會呼叫 resolve() 並傳回數據。
  3. Rejected (已拒絕):操作失敗。Promise 會呼叫 reject() 並傳回報錯原因。
狀態不可逆:一旦 Promise 的狀態從 Pending 轉變為成功或失敗,就「定型」了,後續無法再次修改狀態。

建立與消費 Promise

你可以手動將一個帶有 Callback 的舊 API 封裝成 Promise:

/* 封裝一個簡易的讀取進度 Promise */
const checkDatabase = new Promise((resolve, reject) => {
  const isOnline = true;

  setTimeout(() => {
    if (isOnline) {
      resolve('資料庫連線正常'); // 成功!
    } else {
      reject(new Error('資料庫無法連線')); // 失敗!
    }
  }, 1500);
});

// 使用 .then() 與 .catch()
checkDatabase
  .then((msg) => {
    console.log('成功:', msg);
  })
  .catch((err) => {
    console.error('出錯:', err.message);
  })
  .finally(() => {
    console.log('連線檢查結束');
  });

Promise 鏈結 (Chaining):讓程式碼變垂直

Promise 最強大的功能是鏈結。每一個 .then() 如果回傳一個新的 Promise,下一個 .then() 就會等待它完成。這讓非同步任務可以像「接力賽」一樣撰寫:

// 鏈結範例:讀取 -> 處理 -> 傳送
fetchUserData(userId)
  .then((user) => {
    console.log('用戶獲取成功');
    return processProfile(user); // 必須 return,下一層才會接收到 Promise
  })
  .then((processedData) => {
    return saveToDatabase(processedData);
  })
  .then(() => {
    console.log('所有任務已完成!');
  })
  .catch((err) => {
    // 這一行能捕獲「整條鏈結」中任何一個階段發生的錯誤
    console.error('流程失敗:', err);
  });

常用的 Promise 靜態方法

除了基本用法,現代 JavaScript 還提供了更豐富的控制工具:

  • Promise.all([p1, p2, p3])所有任務都成功才算成功;只要有一個失敗就立即報錯。常用於並行獲取多個相互獨立的數據。
  • Promise.allSettled([...]):等到所有任務都「結束」(無論成敗),並回傳每個任務的詳細狀態。
  • Promise.race([...]):誰最快跑完就用誰的結果,不論成敗。
  • Promise.any([...]):只要其中一個成功就回傳;如果全部失敗才報錯。

Node.js 中的 Promise 最佳實踐

現在 Node.js 的核心模組(如 fs, dns, timers)都提供了原生的 Promise 版本:

// 使用內建的 fs/promises 模組
const fs = require('fs').promises;

fs.readFile('config.json', 'utf8')
  .then((data) => console.log(data))
  .catch((err) => console.error(err));

雖然 Promise 鏈結解決了嵌套問題,但在複雜邏輯下,過長的 .then() 依然會顯得有些凌亂。為了讓非同步代碼寫起來更像「同步代碼」,目前業界最推薦搭配 Async / Await 來使用。