JAVASCRIPT V2 · ASYNC · 42
Promise / async / await:把「之後才有答案」變成可組合流程
13 已經建立「start now, continue later」的時間直覺。現在把 callback 升級成 Promise:一個有明確 lifecycle、可串接成功與失敗、能被 await 消費的未來結果。
Learning outcomes
- 能追蹤 Promise 的 pending → fulfilled/rejected。
- 理解
thencallback 的 return value 如何形成下一個 Promise。 - 理解
asyncfunction 永遠回 Promise。 - 能正確使用
await、try/catch與Promise.all。 - 能區分 HTTP error response 與 Promise rejection。
- 能讀出 rejection propagation 的真正 root cause。
1. Why now:為什麼 callback 之後需要 Promise?
loadUser(userId, user => {
loadOrders(user.id, orders => {
saveSummary(orders, () => {
// ...
});
});
});
Promise 把「未來成功值 / 失敗原因」變成 object,讓流程可以 return、chain、await、aggregate。
2. Mental model:Promise 是狀態機,不是資料本身
pending
├── fulfill(value) ──→ fulfilled
└── reject(error) ──→ rejected
settled = fulfilled or rejected
settled 後不再改變結果
const p = fetch("/api/students");
console.log(p);
// Promise,不是 students array3. Worked Example A:then 不是「等一下執行這段」而已
Promise.resolve(10)
.then(value => {
return value * 2;
})
.then(value => {
console.log(value); // 20
});
第一個 then 會產生一個新的 Promise。callback return 20,下一個 Promise 就 fulfill 20;如果 callback throw,下一個 Promise 就 reject。
4. Execution trace:value 與 error 都會沿 chain 流動
Promise.resolve(5)
.then(n => n + 1)
.then(n => {
throw new Error("boom");
})
.then(n => n * 10)
.catch(error => {
return 99;
})
.then(n => console.log(n));
| step | input | result |
|---|---|---|
| resolve | — | fulfilled 5 |
| then #1 | 5 | fulfilled 6 |
| then #2 | 6 | rejected Error("boom") |
| then #3 | skipped | 仍 rejected |
| catch | error | fulfilled 99 |
| then #4 | 99 | log 99 |
5. async function:把 return / throw 對映到 Promise
async function answer() {
return 42;
}
const result = answer();
// Promise<42>
async function fail() {
throw new Error("bad");
}
// returned Promise is rejected
這是理解 async/await 最重要的規則之一:async function 的外部 contract 是 Promise。
6. await:把 Promise result 重新帶回區域流程
async function loadStudent() {
const response =
await fetch("/api/student/7");
const data =
await response.json();
return data;
}
await 只暫停這個 async function 的 continuation。主執行緒並沒有因此「同步睡著」。真正恢復順序會在 26 Event Loop 深挖。
7. HTTP 500 ≠ fetch rejection
const response =
await fetch("/api/report");
if (!response.ok) {
throw new Error(
"HTTP " + response.status
);
}
const data =
await response.json();
| 事件 | fetch Promise | 你應做什麼 |
|---|---|---|
| 200 | fulfilled | parse body |
| 404 / 500 | 通常 fulfilled | 檢查 response.ok/status |
| DNS / connect / abort | rejected | catch error |
8. Worked Example B:try/catch 管的是 rejection/throw
async function refresh() {
setStatus("loading");
try {
const students =
await loadStudents();
render(students);
setStatus("success");
} catch (error) {
console.error(error);
setStatus("error");
}
}
如果 loadStudents 沒有把 HTTP 500 主動 throw,這個 catch 不會神奇知道 status 500 是失敗。
9. Worked Example C:serial 與 parallel waiting
// serial
const user =
await loadUser();
const courses =
await loadCourses();
// independent work in parallel
const [user, courses] =
await Promise.all([
loadUser(),
loadCourses()
]);
只有兩個工作互不依賴時才適合同時 start。不要為了「看起來進階」把真正有 dependency 的流程硬改成 Promise.all。
10. Promise.all 的 failure contract
await Promise.all([
loadUser(),
loadCourses(),
loadSettings()
]);
任一 input Promise reject,整個 Promise.all 會 reject。其他外部工作可能已經開始甚至仍在執行;Promise.all 並不等於自動 cancellation。
11. Common mistakes
A. 忘記 return Promise
function load() {
fetch("/api/data")
.then(r => r.json());
}
await load(); // 立刻得到 undefined
B. await 放在 forEach callback 裡卻以為外層會等
items.forEach(async item => {
await save(item);
});
// outer code does not await all callbacks
C. catch 後吞掉 error
catch (error) {
console.error(error);
// caller sees fulfilled undefined
}
D. 把 HTTP 500 當 network reject
先檢查 response.ok。
12. Debug evidence:Unhandled rejection 要追第一個 throw
Uncaught (in promise)
TypeError: Cannot read properties
of undefined (reading 'id')
at normalizeUser (user.js:18)
at loadUser (api.js:42)
at async boot (app.js:9)
Stack trace 已經給出 evidence chain。先看最早進入你自己的錯誤 function,而不是在 boot() 外面亂加 try/catch。
13. Guided exercise:修掉 missing return
function getScore() {
Promise.resolve(80)
.then(score => score + 5);
}
getScore().then(console.log);
說明為何這段不是「Promise 太快」,並修到能輸出 85。
14. Independent exercise:三個 endpoint 的 dashboard
寫一個 async function,同時取得 profile、courses、settings。要求:
- 三個 request 互不依賴,使用 Promise.all。
- 任一 HTTP response 非 2xx,要丟出含 status 的 Error。
- UI 明確有 loading / success / error。
15. Challenge:partial success 要不要 Promise.all?
如果 dashboard 即使 settings 失敗仍要顯示 profile,你會選 Promise.all、Promise.allSettled,還是各自 try/catch?先定義產品 contract,再選 API。
Project checkpoint:Grade Tracker API pipeline
click Refresh
↓
status = loading
↓
loadStudents()
↓
HTTP response check
↓
JSON parse
↓
students state
↓
render
↓
status = success
any throw/rejection
↓
status = error16. Vocabulary / Summary
- Promise:未來 settlement 的 object contract。
- fulfilled / rejected:兩種 settled state。
- chaining:then/catch 產生新的 Promise pipeline。
- propagation:value 或 rejection 沿 chain 傳遞。
- aggregate:Promise.all / allSettled 等組合多個 Promise。
17. Further reading
Knowledge check · Mastery
- 追蹤一條含 return、throw、catch 的 Promise chain。
- 解釋 async function 為什麼即使 return 42,外部仍拿到 Promise。
- 區分 HTTP 500 與 network rejection。
- 完成頁面下方 Executable JavaScript mastery tests。