JS – Callback Hell

回呼地獄 Callback Hell,又稱「毀滅金字塔」(Pyramid of Doom)

簡單來說,它是指當你需要連續執行多個非同步操作(例如:先讀取檔案 A,拿到 A 的內容後再去查資料庫 B,查完資料庫再呼叫 API C…),且每個操作都依賴前一個操作的結果時,你的程式碼會不斷地向右縮進,假設我們現在要辦理一個會員註冊流程,背後需要做三件事:

  1. 驗證使用者密碼
  2. 撈取該使用者的個人資料
  3. 更新他的最後登入時間

用傳統的 Callback 寫法,程式碼會變成這樣:

// 經典的金字塔形狀
verifyUser(username, password, function(err, userInfo) {
    if (err) {
        handleError(err);
    } else {
        getUserProfile(userInfo.id, function(err, profile) {
            if (err) {
                handleError(err);
            } else {
                updateLoginStatus(profile.id, function(err, result) {
                    if (err) {
                        handleError(err);
                    } else {
                        console.log("全部成功!新狀態為:", result);
                        // 如果後面還有第 4、第 5 個步驟...程式碼會繼續往右邊無限延伸
                    }
                });
            }
        });
    }
});Code language: JavaScript (javascript)

為什麼 Callback Hell 是個大問題?

  1. 極度難以閱讀: 程式碼不是往下發展,而是往右發展。當層層嵌套到 5 層以上時,你光是找哪一個大括號 {} 對應哪一個,眼睛就會瞎掉。
  2. 錯誤處理(Error Handling)非常痛苦: 你必須在「每一層」都寫一次 if (err)。只要漏掉一層,程式抓到錯誤時就會直接崩潰,而且很難追蹤到底是哪一個步驟出錯。
  3. 難以維護與重構: 如果今天想把「步驟 2」和「步驟 3」的順序對調,或者中間想安插一個新步驟,你得大費周章地拆解、移動整塊金字塔,極易寫出 Bug。

解決方法

JavaScript 後來推出了兩個重大的語法更新來徹底解決這個問題:

方案 A:使用 Promise(點對點串聯)

把非同步操作包裝成 Promise 物件,程式碼就可以用 .then()「往下」鏈接,擺脫向右縮進。

verifyUser(username, password)
  .then(userInfo => getUserProfile(userInfo.id))
  .then(profile => updateLoginStatus(profile.id))
  .then(result => console.log("全部成功!", result))
  .catch(err => handleError(err)); // 所有的錯誤,只要在最後用一個 catch 就能統一捕捉!Code language: JavaScript (javascript)

方案 B:使用 Async / Await(終極優雅版)

也是目前商智中心和我個人的寫法,它讓非同步程式碼看起來就像同步程式碼一樣乾淨

async function handleUserLogin() {
  try {
    const userInfo = await verifyUser(username, password);
    const profile = await getUserProfile(userInfo.id);
    const result = await updateLoginStatus(profile.id);
    console.log("全部成功!", result);
  } catch (err) {
    handleError(err); // 一個 try...catch 搞定所有步驟的錯誤
  }
}Code language: JavaScript (javascript)

1 comment