Promise 與 async/await
同步 vs 非同步
比喻: 🍳
- 同步:煎完蛋 → 才能烤吐司 → 才能倒咖啡(一件接一件)
- 非同步:同時煎蛋 + 烤吐司 + 泡咖啡(不用等前一件做完)
JavaScript 是單執行緒,但透過非同步機制避免「卡住」。
回呼地獄(Callback Hell)
// 舊方式:巢狀回呼
getUser(1, function(user) {
getOrders(user.id, function(orders) {
getOrderDetails(orders[0].id, function(details) {
console.log(details);
// 越來越深...這就是回呼地獄 🔥
});
});
});
Promise — 解決回呼地獄
// Promise 有三種狀態:
// pending(等待中)→ fulfilled(成功)或 rejected(失敗)
// 建立 Promise
function fetchUser(id) {
return new Promise((resolve, reject) => { // ← 建立 Promise
setTimeout(() => {
if (id > 0) {
resolve({ id, name: "小明" }); // ← 成功,傳出資料
} else {
reject(new Error("ID 無效")); // ← 失敗,傳出錯誤
}
}, 1000);
});
}
// 使用 Promise
fetchUser(1)
.then(user => { // ← 成功時執行
console.log(user.name);
return fetchOrders(user.id); // ← 回傳新的 Promise
})
.then(orders => { // ← 鏈式呼叫
console.log(orders);
})
.catch(error => { // ← 失敗時執行(任何一步失敗都會到這)
console.error("錯誤:", error.message);
})
.finally(() => { // ← 不管成功失敗都執行
console.log("完成");
});
async / await — Promise 的語法糖
// async 函式永遠回傳 Promise
async function getUser(id) {
// await 等待 Promise 完成,取得結果
let response = await fetch(`/api/users/${id}`); // ← 等待 HTTP 請求完成
let user = await response.json(); // ← 等待 JSON 解析完成
return user; // ← 自動包成 Promise
}
// 呼叫
async function main() {
try {
let user = await getUser(1); // ← 等待結果
console.log(user.name);
let orders = await getOrders(user.id); // ← 繼續下一步
console.log(orders);
} catch (error) { // ← 錯誤處理
console.error("出錯了:", error.message);
}
}
main();
對比
// Promise 鏈
fetchUser(1)
.then(user => fetchOrders(user.id))
.then(orders => console.log(orders))
.catch(err => console.error(err));
// async/await(更像同步程式碼,更好讀)
async function main() {
let user = await fetchUser(1);
let orders = await fetchOrders(user.id);
console.log(orders);
}
並行執行
// ❌ 串行(等 A 完才跑 B)— 慢
async function slow() {
let userA = await fetchUser(1); // 等 1 秒
let userB = await fetchUser(2); // 再等 1 秒
// 總共 2 秒
}
// ✅ 並行(A 和 B 同時跑)— 快
async function fast() {
let [userA, userB] = await Promise.all([
fetchUser(1), // 同時開始
fetchUser(2) // 同時開始
]);
// 總共 1 秒
}
// Promise.allSettled — 全部完成(不管成功失敗)
let results = await Promise.allSettled([
fetchUser(1), // 成功
fetchUser(-1), // 失敗
]);
// results[0] = { status: "fulfilled", value: {...} }
// results[1] = { status: "rejected", reason: Error }
// Promise.race — 最快的那個
let fastest = await Promise.race([
fetchFromServer1(),
fetchFromServer2()
]);
// 回傳最先完成的結果
實用模式
// 帶超時的請求
async function fetchWithTimeout(url, ms) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), ms);
try {
let response = await fetch(url, { signal: controller.signal });
return await response.json();
} finally {
clearTimeout(timeout);
}
}
// 重試機制
async function fetchWithRetry(url, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
return await fetch(url).then(r => r.json());
} catch (err) {
if (i === retries - 1) throw err;
await new Promise(r => setTimeout(r, 1000 * (i + 1)));
}
}
}