← JavaScript Learning Path

JAVASCRIPT V2 · ASYNC · 42

Promise / async / await:把「之後才有答案」變成可組合流程

13 已經建立「start now, continue later」的時間直覺。現在把 callback 升級成 Promise:一個有明確 lifecycle、可串接成功與失敗、能被 await 消費的未來結果。

Learning outcomes

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 array

3. 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));
stepinputresult
resolve—fulfilled 5
then #15fulfilled 6
then #26rejected Error("boom")
then #3skipped仍 rejected
catcherrorfulfilled 99
then #499log 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你應做什麼
200fulfilledparse body
404 / 500通常 fulfilled檢查 response.ok/status
DNS / connect / abortrejectedcatch 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。要求:

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 = error

16. Vocabulary / Summary

17. Further reading

Knowledge check · Mastery

  1. 追蹤一條含 return、throw、catch 的 Promise chain。
  2. 解釋 async function 為什麼即使 return 42,外部仍拿到 Promise。
  3. 區分 HTTP 500 與 network rejection。
  4. 完成頁面下方 Executable JavaScript mastery tests。