[Medium] 📄 Promise
什麼是 Promise?
Promise 是 ES6 的新特性,主要是用來解決 callback hell 的問題,並且讓程式碼更容易閱讀。Promise 代表一個非同步操作的最終完成或失敗,以及其結果值。
Promise 有三種狀態:
- pending(進行中):初始狀態
- fulfilled(已完成):操作成功完成
- rejected(已拒絕):操作失敗
基本用法
建立 Promise
const myPromise = new Promise((resolve, reject) => {
// 非同步操作
const success = true;
if (success) {
resolve('成功!'); // 將 Promise 狀態改為 fulfilled
} else {
reject('失敗!'); // 將 Promise 狀態改為 rejected
}
});
myPromise
.then((result) => {
console.log(result); // '成功!'
})
.catch((error) => {
console.log(error); // '失敗!'
});
實際應用:處理 API 請求
// 建立一個共用 function 來處理 api 請求
function fetchData(url) {
return fetch(url)
.then((response) => {
// 檢查 response 是否落在 200 ~ 299 的區間
if (!response.ok) {
throw new Error('Network response was not ok ' + response.statusText);
}
return response.json(); // 將 response 轉成 json,並回傳
})
.catch((error) => {
// 檢查網路是否異常,或者請求被拒絕
console.log('There has been a problem with your fetch operation:', error);
throw error; // 將錯誤拋出
});
}
fetchData('https://jsonplaceholder.typicode.com/users/1')
.then((userData) => {
console.log('User data received:', userData);
})
.catch((error) => {
console.log('Error:', error.message);
});
Promise 的方法
.then() / .catch() / .finally()
promise
.then((result) => {
// 處理成功的情況
return result;
})
.catch((error) => {
// 處理錯誤
console.error(error);
})
.finally(() => {
// 無論成功或失敗都會執行
console.log('Promise 完成');
});
Promise.all()
當所有 Promise 都完成時才會完成,只要有一個失敗就會失敗。
const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve) =>
setTimeout(() => resolve('foo'), 100)
);
const promise3 = Promise.resolve(42);
Promise.all([promise1, promise2, promise3]).then((values) => {
console.log(values); // [3, 'foo', 42]
});
使用時機:需要等待多個 API 請求都完成後才繼續執行。
Promise.race()
回傳第一個完成(無論成功或失敗)的 Promise 結果。
const promise1 = new Promise((resolve) =>
setTimeout(() => resolve('一號'), 500)
);
const promise2 = new Promise((resolve) =>
setTimeout(() => resolve('二號'), 100)
);
Promise.race([promise1, promise2]).then((value) => {
console.log(value); // '二號'(因為較快完成)
});
使用時機:設定請求超時、只取最快回應的結果。
Promise.allSettled()
等待所有 Promise 完成(無論成功或失敗),回傳所有結果。
const promise1 = Promise.resolve(3);
const promise2 = Promise.reject('錯誤');
const promise3 = Promise.resolve(42);
Promise.allSettled([promise1, promise2, promise3]).then((results) => {
console.log(results);
// [
// { status: 'fulfilled', value: 3 },
// { status: 'rejected', reason: '錯誤' },
// { status: 'fulfilled', value: 42 }
// ]
});
使用時機:需要知道所有 Promise 的執行結果,即使某些失敗也要繼續處理。
Promise.any()
回傳第一個成功的 Promise,所有都失敗才會失敗。
const promise1 = Promise.reject('錯誤 1');
const promise2 = new Promise((resolve) =>
setTimeout(() => resolve('成功'), 100)
);
const promise3 = Promise.reject('錯誤 2');
Promise.any([promise1, promise2, promise3]).then((value) => {
console.log(value); // '成功'
});
使用時機:多個備用資源,只要有一個成功即可。
面試題目
題目 1:Promise 鏈式調用與錯誤處理
試判斷以下程式碼的輸出結果:
Promise.resolve(1)
.then((x) => x + 1)
.then(() => {
throw new Error('My Error');
})
.catch((e) => 1)
.then((x) => x + 1)
.then((x) => console.log(x))
.catch((e) => console.log('This will not run'));
解析
讓我們逐步分析執行過程:
Promise.resolve(1) // 回傳值:1
.then((x) => x + 1) // x = 1,回傳 2
.then(() => {
throw new Error('My Error'); // 拋出錯誤,進入 catch
})
.catch((e) => 1) // 捕捉錯誤,回傳 1(重要:這裡回傳的是正常值)
.then((x) => x + 1) // x = 1,回傳 2
.then((x) => console.log(x)) // 輸出 2
.catch((e) => console.log('This will not run')); // 不會執行
答案:2
關鍵概念
- catch 會捕捉錯誤並回傳正常值:當
catch()回傳一個正常值時,Promise 鏈會繼續執行後續的.then() - catch 之後的 then 會繼續執行:因為錯誤已經被處理,Promise 鏈恢復正常狀態
- 最後的 catch 不會執行:因為沒有新的錯誤被拋出
如果想讓錯誤繼續傳遞,需要在 catch 中重新拋出錯誤:
Promise.resolve(1)
.then((x) => x + 1)
.then(() => {
throw new Error('My Error');
})
.catch((e) => {
console.log('捕捉到錯誤');
throw e; // 重新拋出錯 誤
})
.then((x) => x + 1) // 不會執行
.then((x) => console.log(x)) // 不會執行
.catch((e) => console.log('This will run')); // 會執行