[JS] Promise
Promise 基本介紹
Promise 是一個物件,代表非同步操作的結果,當我們發起一個非同步操作時,Promise 會在未來的某個時間點給我們一個結果。
如何創建 Promise
要使用 new 關鍵字來創建 Promise 物件,這個被稱為 excutor ,excutor 需要傳入一個函式,這個函式會立刻執行,而它包含兩個參數 resolve 和 reject,分別代表非同步操作成功和失敗的回調函數,
- 當執行
resolve時:狀態會從 pending 轉變為 fulfilled,並且會執行resolve的回調函數 - 當執行
reject時:狀態會 從 pending 轉變為 rejected,並且會執行reject的回調函數 - 當沒有執行
resolve或reject時:狀態會一直保持在 pending 狀態,直到有結果為止
const promise = new Promise((resolve, reject) => {
// fullfilled
resolve('成功');
// rejected
reject('失敗');
});
狀態一旦從 pending 轉變為 fulfilled 或 rejected 後就不會再改變。
為什麼要特別強調這一點呢?因為這是 Promise 重要的特性之一,它的狀態是有可預期性,以下面簡單例子來說,
const promise = new Promise((resolve, reject) => {
setTimeout(() => {reject('失敗')},1000)
resolve('失敗');
});
即便有一個非同步操作在 1 秒後執行 reject('失敗'),Promise 的狀態也不會從 fulfilled 轉變為 rejected,而是保持 fulfilled 狀態。
Promise 的方法
透過 then 方法可承接 excutor 中 fullfilled / rejected 狀態的回傳值,它提供兩個參數,分別作為 fullfilled 和 rejected 的回調函數
promise.then(fullfilledHandler, rejectedHandler)
除此之外,catch 是專門用來處理 rejected 狀態的方法
promise.catch(rejectedHandler)
當 then 中觸發 rejected 狀態時,catch 會捕捉到這個錯誤,並中斷後續 then 的執行,直接執行 catch 的回調函數,如下面範例:
new Promise((resolve, reject) => {
resolve("Step 1: Success");
})
.then((result) => {
console.log(result); // 打印第一個步驟的結果
return "Step 2: Success"; // 傳遞給下一個 .then()
})
.then((result) => {
console.log(result); // 打印第二個步驟的結果
throw new Error("Step 2: Error occurred!"); // 在這裡故意拋出錯誤
})
.then((result) => {
// 這個步驟將不會執行,因為前面已經有錯誤
console.log(result);
return "Step 3: Success";
})
.catch((error) => {
// 錯誤會被這裡的 .catch() 捕獲
console.error("Caught error:", error.message);
});
剩餘 all / race / any / allSettled 都是 Promise 用來處理多個 Promise 組合的方法。它們的差異在於:
Promise.all():只有當所有原始的 Promise 都成功時,才會成功 resolve,否則只要有一個 Promise 失敗,就會立刻 reject。Promise.race():只要有一個原始的 Promise 成功或失敗,就可成功 resolvePromise.any():只要有一個原始的 Promise 成功,新的 Promise 就會成功,否則只要所有原始的 Promise 都失敗,新的 Promise 就會失敗。Promise.allSettled():不管原始的 Promise 是成功還是失敗,新的 Promise 都會成功,並且會包含所有原始 Promise 的結果。
以及 finally 方法,不論原始的 Promise 是成功還是失敗,新的 Promise 都會 執行 finally 的回調函數。
如何選擇 catch 或 then 作錯誤處理?
上面提到 catch 和 then 都可以用來處理錯誤,那麼到底該選擇哪一個呢?
promise.then(fullfilledHandler, rejectedHandler)
// 等同於
promise.then(fullfilledHandler).catch(rejectedHandler)
最簡單來說就是
- 是否會有不同錯誤情境要處理?
- 錯誤訊息格式是否一致?
假設你符合上述任一情境,那麼 then 會是比較好的選擇。
舉個例子,假設今天你要串接兩個 API,當第一個 API 讀取失敗時,你要打一隻名為 notifyFailure API 通知後端,但第二個 API 讀取失敗時,則不須通知後端僅需要回傳錯誤訊息,使用 then 能個別處理這兩種情境。
new Promise((resolve, reject) => {
// 模擬第一個 API 讀取失敗
reject('Error from API 1');
})
.then(result => {
// 第一隻 API 讀取成功時,執行第二隻 API
return 'API 2 Success';
}, error => {
// 第一隻 API 讀取失敗
notifyFailure(error);
}).then(result => {
// 第二隻 API 讀取成功
return 'API 2 Success';
}, error => {
// 第二隻 API 讀取失敗
console.log(error);
})