正在載入索引…

JavaScript:深入理解Promise的原理及實際應用

5.5k 字,約 5 分鐘

文章目錄
  1. Promise 為了解決什麼問題
  2. Promise 狀態
  3. Promise 靜態方法
    1. Promise.resolve(value) & Promise.reject(reason)
    2. Promise.all(iterable)
    3. Promise.race(iterable)
  4. async/await

Promise 為了解決什麼問題

  • JavaScript Promise 是一種用來處理異步操作的對象,它代表一個異步操作的成功(resolve)或失敗(reject)的結果。(以下皆使用 setTimeout()示範異步操作的案例)
  • 透過這種方法能避免回調地獄(Callback Hell)的問題。
  • 以下直接透過 Callback Hell 的函數說明:
javascript
// 假設有一個需求,我們需要連續執行三個異步操作:A、B、C。
function operationA(callbackA) {
    setTimeout(() => {
      console.log("Operation A completed");
      callbackA();
    }, 1000);  //← 等待1秒執行
  }
  function operationB(callbackB) {
    setTimeout(() => {
      console.log("Operation B completed");
      callbackB();
    }, 1500);  //← 等待1.5秒執行
  }
  function operationC(callbackC) {
    setTimeout(() => {
      console.log("Operation C completed");
      callbackC();
    }, 2000);  //← 等待2秒執行
  }
// Callback Hell
  operationA(() => {
    operationB(() => {
      operationC(() => {
        console.log("All operations completed");
      });
    });
  });
//   在這個例子中,operationA 完成後,我們才能開始 operationB,然後是 operationC。這種嵌套結構會使得代碼難以閱讀,特別是當有更多異步操作時,這種形式會變得更加複雜和難以管理,這就是所謂的 Callback Hell。

顯示如下(依序出現 A->B->C->All):

javascript
Operation A completed
Operation B completed
Operation C completed
All operations completed

這邊特別強調多層次結構的 Callback Hell 會變成像是波動拳的結構!!!難以維護


  • Promise 的寫法能夠避免內嵌的回調函數寫法
javascript
function operationA() {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log("Operation A completed");
      resolve();
    }, 1000);  //← 等待1秒執行
  });
}
function operationB() {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log("Operation B completed");
      resolve();
    }, 1500);  //← 等待1.5秒執行
  });
}
function operationC() {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log("Operation C completed");
      resolve();
    }, 2000);
  });
}
// 使用 Promise 的方式
operationA()
  .then(() => operationB())
  .then(() => operationC())
  .then(() => console.log("All operations completed"))
  .catch((error) => console.error("Error:", error));

顯示如下(依序出現 A->B->C->All)

javascript
Operation A completed
Operation B completed
Operation C completed
All operations completed

Promise 狀態

  • 當你使用 Promise 時,它可以處於三種不同的狀態(狀態改變後就不可逆):
    • Peding(等待中)
      • 創建使用的初始狀態,可在內部放置異步事件
    • Fulfiled(已完成)
      • 異步事件完成時,透過 resolve()方法將 Promise 狀態改變至 Fulfiled(已完成)
      • 此時可以透過.then 回傳 Promise 執行的結果
    • Rejected(已拒絕)
      • 異步事件失敗時,透過 reject()方法將 Promise 狀態改變至 Rejected(已失敗)
      • 此時可透過.catch 處理 Promise 的錯誤訊息
  • .finally()---最終結果
    • 不論完成或是被拒絕均會執行的回調函數

  • 基本使用概念如下

javascript
operationA()
  .then((res) => {
    console.log("操作成功:", res);
  })
  .catch((error) => {
    console.error("錯誤:", error);
  })
  .finally(() => {
    console.log("不論成功還是失敗都會執行的代碼");
  });

  • Fulfilled 案例(如果 Nb 小於 10 代表成功)
javascript
function operationA(Number) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (Number < 10) {
        resolve({ Status: "OK" });
      } else {
        reject({ Status: "Error" });
      }
    }, 1000);  //← 等待1秒執行
  });
}
// 使用 Promise 的方式
const Nb = 5
operationA(Nb)
  .then((res) => {
    console.log("異步事件-正確執行回傳資料", res)
  })
  .catch((error) => {
    console.error("異步事件-異常訊息:", error)
  })

顯示如下(.then()捕獲異步事件回傳資料):

javascript
異步事件-正確執行回傳資料 { Status: 'OK' }

  • reject 案例(如果 Nb 大於 10 代表成功):
javascript
function operationA(Number) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (Number < 10) {
        resolve({ Status: "OK" });
      } else {
        reject({ Status: "Error" });
      }
    }, 1000);
  });
}
// 使用 Promise 的方式
const Nb = 20
operationA(Nb)
  .then((res) => {
    console.log("異步事件-正確執行回傳資料", res)
  })
  .catch((error) => {
    console.error("異步事件-異常訊息:", error)
  })

顯示如下(透過.catch()捕獲異步事件錯誤訊息):

javascript
異步事件-異常訊息: { Status: 'Error' }

  • Promise 的鏈式調用(多個異步事件同步執行):
javascript
function operationA(Number) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({ Number: Number * 2 });
    }, 1000);  //← 等待1秒執行
  });
}
// 使用 Promise 的方式
const Nb = 20
operationA(Nb)
  .then((res) => {
    console.log("第一階段-異步事件-正確執行回傳資料", res)
    return operationA(res.Number)
  })
  .then((res) => {
    console.log("第二階段-異步事件-正確執行回傳資料", res)
  })
  .catch((error) => {
    console.error("異步事件-異常訊息:", error)
  })

顯示如下(確保第一個事件執行完接收 res.Number,進行第二次異步事件):

javascript
第一階段-異步事件-正確執行回傳資料 { Number: 40 }
第二階段-異步事件-正確執行回傳資料 { Number: 80 }

Promise 靜態方法

Promise.resolve(value) & Promise.reject(reason)

  • Promise.resolve(value):回傳 Promise 已完成的結果
  • Promise.reject(reason):回傳 Promise 被拒絕的結果
javascript
Promise.resolve("Reject Result")
  .then((res) => {
    console.log("Resolved:", res);  // 這裡會執行
  })
  .catch((error) => {
    console.log("Rejected:", error);  // 這裡不會執行
  });

Promise.reject("Reject Result")
  .then((res) => {
    console.log("Resolved:", res);  // 這裡不會執行
  })
  .catch((error) => {
    console.log("Rejected:", error);  // 這裡會執行
  });

顯示如下:

javascript
Resolved: Reject Result
Rejected: Reject Result

Promise.all(iterable)

  • 等待所有 Promise 狀態轉變成 Fulfiled 取得的結果,返回一個包含所有解析值的數組。
  • 如果其中一個 Promise 狀態轉變成 Rejected,整個 Promise.all()都會被拒絕。
javascript
function operationA() {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log("Operation A completed");
      resolve({ Statue: "A" });
    }, 1000);  //← 等待1秒執行
  });
}
function operationB() {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log("Operation B completed");
      resolve({ Statue: "B" });
    }, 1500);  //← 等待1.5秒執行
  });
}
Promise.all([operationA(), operationB()])
  .then((res) => {
    console.log("res", res)
  })
  .catch((error) => {
    console.error("Error", error);
  });

顯示如下:

javascript
Operation A completed
Operation B completed
res [ { Statue: 'A' }, { Statue: 'B' } ]

Promise.race(iterable)

  • 只要其中一個 Promise 狀態轉變成 Fulfiled 或 Rejected 立即結束,返回第一個成功(resolve)或被拒絕(reject)的 Promise。
javascript
function operationA() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({ Statue: "A" });
    }, 500);  //← 等待0.5秒執行
  });
}
function timeout(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject({ Statue: "Timeout" });
    }, ms);
  });
}
Promise.race([operationA(), timeout(1000)])
  .then((res) => {
    console.log("res", res)
  })
  .catch((error) => {
    console.error("Error", error);
  });

顯示如下:

  • 如果超過 timeout 的時間就算是超時
javascript
res { Statue: 'A' }

async/await

  • 由 async 函數及 await 運算子組成,用於處理異步操作的語法糖。
  • await 會解析 Promise 中的 Fulfiled 完成狀態回傳值
  • 將 async/await 用法與 Promise 鍊式寫法相互比較案例如下:
javascript
function operationA(Number) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({ Number: Number * 2 });
    }, 1000);  //← 等待1秒執行
  });
}
// 使用 Promise 鍊式的寫法
// const Nb = 20
// operationA(Nb)
//   .then((res) => {
//     console.log("第一階段-異步事件-正確執行回傳資料", res)
//     return operationA(res.Number)
//   })
//   .then((res) => {
//     console.log("第二階段-異步事件-正確執行回傳資料", res)
//   })
//   .catch((error) => {
//     console.error("異步事件-異常訊息:", error)
//   })
// 使用Async/await的寫法

async function fetchData() {
  try {
    const Nb = 20;
    const result1 = await operationA(Nb);
    console.log("第一階段-異步事件-正確執行回傳資料", result1);
    const result2 = await operationA(result1.Number);
    console.log("第二階段-異步事件-正確執行回傳資料", result2);
  } catch (error) {
    console.error("異步事件-異常訊息:", error);
  }
}

fetchData();

顯示如下:

javascript
第一階段-異步事件-正確執行回傳資料 { Number: 40 }
第二階段-異步事件-正確執行回傳資料 { Number: 80 }

留言

留言使用 GitHub 帳號登入(Giscus),留言會存在 GitHub Discussions。