async/await 是 Promise 的語法糖,讓非同步程式碼讀起來像同步。底層仍是 Promise,事件循環沒有改變,只是寫法不同。

async/await 的基本概念

async 函式:使用 async 關鍵字宣告,無論函式本身回傳什麼值,呼叫端拿到的一定是 Promise。

await 運算符:等待一個 Promise 決議(resolve 或 reject)。在標準模式下只能用在 async 函式內;ES2022 起,ES 模組的頂層也可以直接使用 await,不需要包一層 async 函式。

與 Promise 的關係

async/await 本質上是 Promise 的語法糖。看個對照例子:

使用 Promise:

1
2
3
4
5
6
7
8
9
10
11
function fetchData() {
return fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => {
console.log(data);
return data;
})
.catch(error => {
console.error('發生錯誤:', error);
});
}

使用 async/await:

1
2
3
4
5
6
7
8
9
10
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);
return data;
} catch (error) {
console.error('發生錯誤:', error);
}
}

.then() 鏈需要追蹤回呼的巢狀結構,async/await 版本按順序往下讀就夠了。

async/await 與事件循環

async/await 讓程式碼看起來像同步,但執行仍是非同步的。遇到 await 時,目前函式暫停、把控制權交回事件循環,其他程式碼可以繼續跑。等 Promise 決議後,函式才從暫停點繼續。Call stack 不會被堵住。

async/await 與 setTimeout

setTimeout 本身不回傳 Promise,所以要先包一層:

1
2
3
4
5
6
7
8
9
10
11
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

async function delayedGreeting(name) {
console.log('開始等待...');
await delay(2000);
console.log(`你好,${name}!`);
}

delayedGreeting('小明');

delay 把 setTimeout 包成 Promise,await 才有東西可以等。這也是用 async/await 整合非原生 Promise API 的標準做法。

async/await 的錯誤處理

用 try/catch 就好,不需要在每個 .then() 後面掛 .catch()

1
2
3
4
5
6
7
8
9
10
11
12
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
if (!response.ok) {
throw new Error('網路回應不正確');
}
return await response.json();
} catch (error) {
console.error('發生錯誤:', error);
// 可以在這裡顯示錯誤訊息給使用者
}
}

注意 response.ok 這個判斷——fetch 只在網路層失敗時才 reject,HTTP 4xx/5xx 不會自動進 catch,要自己檢查。

常見陷阱

await 串行 vs 並行

逐一 await 互相獨立的請求,等待時間會累加:

1
2
3
4
5
6
// 慢:兩個請求串行,總共等 A + B
const a = await fetchA();
const b = await fetchB();

// 快:兩個請求並行,總共等 max(A, B)
const [a, b] = await Promise.all([fetchA(), fetchB()]);

如果 fetchB 不依賴 fetchA 的結果,用 Promise.all 同時發出請求。

async 函式的回傳值

即使函式回傳普通值,呼叫端拿到的仍是 Promise。呼叫時要記得 await,否則你在比較 Promise 物件而不是值:

1
2
3
4
5
6
7
8
9
async function getCount() {
return 42;
}

// 錯:count 是 Promise,不是 42
const count = getCount();

// 對
const count = await getCount();

瀏覽器支援

Chrome 55、Firefox 52、Safari 11 起原生支援,IE 完全不支援。如果專案不需要顧 IE,現在基本上不需要特別轉譯。