首頁
學習紀錄
遊戲心得影視Life書單案件檔案
Side Projects委託作品與二創互動實驗場
Kurau
百百 BLOG
首頁
學習紀錄
遊戲心得影視Life書單案件檔案
Side Projects委託作品與二創互動實驗場
Kurau

Kurau Blog

「隨心而寫,真真假假,都是我」

一個記錄生活、輸出興趣的個人空間。
遊戲、影視、閱讀、學習……每一段體驗都值得留下文字。

頁面導覽

  • 學習紀錄
  • 遊戲心得
  • 影視Life
  • 書單
  • 委託作品與二創
  • Kurau
  • 合作邀請

找到我

歡迎來 Discord 找我聊天!

“曾經發生的事不可能忘記,只是暫時想不起來而已。”-《神隱少女》

© 2026 Kurau All rights reserved

面試考題

Async function-Await 函式

By Kurau·Updated 2026-06-02·8 分鐘閱讀

async function 可以用來定義一個非同步函式,讓這個函式本體是屬於非同步,但其內部以“同步的方式運行非同步”程式碼。

await 則是可以暫停非同步函式的運行(中止 Promise 的運行),直到非同步進入 resolve 或 reject,當接收完回傳值後繼續非同步函式的運行。

async function getData() {
  const data1 = await promiseFn(1); // 因為 await,promise 函式被中止直到回傳
  const data2 = await promiseFn(2);
  console.log(data1, data2); // 1, 成功 2, 成功
}
getData();
JavaScript

async/await 是 Promise 的語法糖

async/await 並不是新的非同步機制,底層仍然是 Promise。它只是讓 Promise 的串接「讀起來像同步程式碼」,本質完全等價。

// .then() 鏈式寫法
function getUser() {
  return fetch('/api/user')
    .then(res => res.json())
    .then(user => fetch(`/api/posts/${user.id}`))
    .then(res => res.json());
}

// async/await 寫法(完全等價,但更易讀)
async function getUser() {
  const res = await fetch('/api/user');
  const user = await res.json();
  const postsRes = await fetch(`/api/posts/${user.id}`);
  return postsRes.json();
}
JavaScript

兩個關鍵等價關係:

async/await等價的 Promise 寫法
async function f() 永遠回傳 Promisefunction f() { return Promise.resolve(...) }
return xPromise.resolve(x)(會 unwrap,回傳的若是 Promise 不會包兩層)
throw errPromise.reject(err)
const v = await pp.then(v => ...)
面試重點
「async/await 比 .then() 好在哪?」——可讀性(避免 callback / .then() 巢狀地獄)、可以用熟悉的 try/catch 與 for/if 控制流程、debug 時 stack trace 更清楚。功能上沒有任何 .then() 做不到而 await 做得到的事。

await 的行為

await p 會做三件事:

  1. 暫停目前 async 函式的執行(但不會阻塞主執行緒,控制權交還給 event loop,其他程式碼照跑)。
  2. 等 p 進入 settled 狀態:resolve → await 回傳 resolved 的值;reject → 在 await 那一行拋出例外。
  3. 把後續程式碼當成 microtask 排入佇列,待 p 完成後恢復執行。
console.log(1);
async function run() {
  console.log(2);
  await null;        // 即使 await 一個非 Promise,後面也會變成 microtask
  console.log(4);    // 這行被排進 microtask queue
}
run();
console.log(3);
// 輸出順序:1 → 2 → 3 → 4
JavaScript
await 一個非 Promise 值
await 任何值都會用 Promise.resolve() 包起來。所以 await 5 是合法的,回傳 5,但仍會讓後續程式碼延後到下一個 microtask。

try/catch 錯誤處理

await 的 Promise 若 reject,會在該行拋出例外,因此可以用同步風格的 try/catch 捕捉。這是 async/await 相對 .then() 的一大優勢。

async function fetchData() {
  try {
    const response = await fetch('/api/data');
    if (!response.ok) throw new Error(`HTTP ${response.status}`); // fetch 不會因 4xx/5xx reject
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('獲取資料失敗:', error);
    throw error; // 重新拋出讓上層處理
  } finally {
    console.log('無論成功失敗都會執行(收尾、關 loading)');
  }
}
JavaScript

常見陷阱:

  • fetch 只在網路層失敗才 reject,HTTP 4xx/5xx 仍是 resolve,response.ok 要自己檢查。
  • 沒被 await 也沒 .catch() 的 reject 會變成 unhandledRejection,可能讓程式崩潰。
  • try/catch 只能抓被 await 的錯誤;若 await 漏了(見下方陷阱),catch 抓不到。

也可以用 .catch() 對單一 await 做局部處理,回傳預設值而不中斷整個函式:

const data = await fetchData().catch(() => null); // 失敗就 fallback 成 null
JavaScript

並行(Promise.all / allSettled)vs 序列

多個互不相依的非同步操作不該一個一個等,否則總耗時 = 各項相加。

// ❌ 序列執行:總耗時 = A + B + C(互不相依卻白等)
const a = await fetchA();
const b = await fetchB();
const c = await fetchC();

// ✅ 並行執行:總耗時 ≈ max(A, B, C)
const [a, b, c] = await Promise.all([fetchA(), fetchB(), fetchC()]);
JavaScript

注意:上面 ✅ 寫法的關鍵是先呼叫(fetchA() 立刻啟動 Promise),再一起 await。下面這種寫法其實還是序列:

// ❌ 假並行,實際仍是序列
const pa = fetchA();      // 啟動
const a = await pa;       // 但這裡就等完了
const pb = fetchB();      // B 等 A 完才啟動
JavaScript

Promise.all vs Promise.allSettled vs Promise.race:

方法行為何時用
Promise.all任一 reject → 立刻整體 reject(其他結果丟失)全部都要成功才有意義
Promise.allSettled永不 reject,回傳每項 {status, value/reason}允許部分失敗,要全部結果
Promise.race第一個 settled(不管成敗)就回傳timeout、取最快的來源
Promise.any第一個 fulfilled 就回傳,全失敗才 reject多來源備援,要任一成功
const results = await Promise.allSettled([fetchA(), fetchB()]);
results.forEach(r => {
  if (r.status === 'fulfilled') console.log('成功', r.value);
  else console.error('失敗', r.reason);
});
JavaScript
Promise.all 的 fail-fast 陷阱
Promise.all 在某項 reject 時會「儘早」reject,但其他 Promise 不會被取消,它們仍在背景跑完。若某項拋錯且沒被處理,可能出現 unhandledRejection。需要容錯時用 allSettled。

常見陷阱

1. 迴圈裡的 await 變成序列

for...of 配 await 是序列執行——每圈都等上一圈完成。若各圈互不相依,這會白白拉長總時間。

// ❌ 序列:100 個請求一個一個跑
for (const id of ids) {
  await fetchItem(id);
}

// ✅ 並行:一次發出,一起等
await Promise.all(ids.map(id => fetchItem(id)));
JavaScript

但若刻意要序列(例如每圈依賴前一圈結果、或要限制併發、避免打爆 server),for...of + await 才是對的選擇。並非並行永遠比較好。

2. forEach 裡的 await 完全無效

Array.prototype.forEach 不會等待 callback 回傳的 Promise,async callback 的 await 形同虛設,外層也無從等它完成。

// ❌ forEach 不 await:外層的 console.log 會先跑,且錯誤無法捕捉
ids.forEach(async (id) => {
  await fetchItem(id);
});
console.log('完成?其實上面都還沒跑完');

// ✅ 用 for...of(序列)或 Promise.all(並行)
for (const id of ids) await fetchItem(id);
// 或
await Promise.all(ids.map(id => fetchItem(id)));
JavaScript

3. 忘記 await

漏掉 await 時,變數會是 Promise 物件本身而非結果值,且 try/catch 抓不到它的 reject。

async function bad() {
  const data = fetchData();        // 漏了 await
  console.log(data);               // Promise { <pending> },不是資料
  console.log(data.name);          // undefined
}

// 例外:若只是要 fire-and-forget 不需結果,可以不 await,
// 但務必補 .catch() 避免 unhandledRejection
fetchData().catch(console.error);
JavaScript

4. 頂層 await(top-level await)

ES2022 起,ES Module 的最頂層可以直接 await,不必包在 async 函式裡。

// ✅ 僅限 ESM(.mjs 或 package.json "type": "module")
const config = await fetch('/config.json').then(r => r.json());
JavaScript

要點:

  • 只在 ES Module 有效,CommonJS(require)不支援,一般 <script> 也不行。
  • 頂層 await 會阻塞該模組的載入,連帶讓 import 它的模組也等待——用不好會拖慢啟動。
  • 在非 module 環境寫頂層 await 會直接 SyntaxError。

.then() 對比小結

面向async/await.then()
可讀性像同步程式碼,扁平易陷入鏈式 / 巢狀
錯誤處理try/catch(同步風格).catch()
控制流程(if/for)自然使用原生語法需在 callback 內處理,較繞
並行仍需搭配 Promise.allPromise.all
本質語法糖,底層就是 Promise原生 Promise API
兩者可混用
async/await 與 .then() 不互斥。常見組合:用 await 寫主流程,對單一 Promise 用 .catch() 設 fallback(如 await fn().catch(() => default))。

注意事項

  • await 只能在 async 函式內使用(或 ESM 最頂層 await,ES2022+)。
  • async 函式永遠回傳 Promise,return x 會被包成 Promise.resolve(x)。
  • 迭代器(forEach)中的 await 不會等待,應改用 for...of(序列)或 Promise.all(並行)。

目錄

    ◆ 相關文章

    • throw Error用法

      2026-06-02
    • TypeScript 特性 - Interface

      2026-06-02
    • TypeScript 資料型別 - 元組(Tuple) & 列舉(Enum)

      2026-06-02
    • 箭頭函數 Arrow function expression

      2026-06-02
    ← 上一篇解釋一下 React的各種Hook下一篇 →throw Error用法

    ◆ 關於作者

    Kurau

    個人寫作 / 創作的 SoT,記錄遊戲、影視、學習與生活。

    更多 Kurau 的文章