Async/Await vs 同步處理的差異
目錄
- 基本概念
- 同步處理 (Synchronous)
- 非同步處理 (Asynchronous)
- Async/Await 的運作原理
- 實際範例比較
- 執行順序視覺化
- Angular 實際應用
- 常見誤解
- 最佳實踐
基本概念
同步處理 (Synchronous)
- 程式碼**逐行執行**,一次只做一件事
- 每一行必須等待上一行**完全執行完畢**
- 會**阻塞**後續程式碼的執行
- UI 會被凍結,無法互動
非同步處理 (Asynchronous)
- 程式碼**不會等待**耗時操作完成
- 可以**同時處理多個任務**
- 不會阻塞 UI 或其他程式碼
- 使用 Promise、Callback 或 Async/Await 處理
Async/Await
- 是 Promise 的**語法糖**(syntactic sugar)
- 讓非同步程式碼**看起來像同步**
- 本質仍然是非同步,不會阻塞執行緒
- 提高程式碼可讀性和維護性
同步處理 (Synchronous)
範例 1:同步執行
function syncExample() {
console.log('步驟 1:開始');
// 模擬耗時操作 (阻塞 3 秒)
const start = Date.now();
while (Date.now() - start < 3000) {
// 什麼都不做,只是浪費時間
}
console.log('步驟 2:耗時操作完成');
console.log('步驟 3:結束');
}
syncExample();
console.log('步驟 4:後續程式碼');
執行結果
步驟 1:開始
(等待 3 秒... UI 凍結!)
步驟 2:耗時操作完成
步驟 3:結束
步驟 4:後續程式碼
問題
- ❌ UI 完全凍結 3 秒
- ❌ 使用者無法點擊按鈕或滾動頁面
- ❌ 整個應用程式停止回應
非同步處理 (Asynchronous)
範例 2:使用 Promise (不使用 await)
function asyncExample() {
console.log('步驟 1:開始');
// 模擬非同步 API 呼叫
fetchDataFromAPI()
.then(data => {
console.log('步驟 2:收到資料', data);
});
console.log('步驟 3:不等待 API,繼續執行');
}
asyncExample();
console.log('步驟 4:後續程式碼');
執行結果
步驟 1:開始
步驟 3:不等待 API,繼續執行
步驟 4:後續程式碼
(1 秒後...)
步驟 2:收到資料 {...}
特點
- ✅ UI 不會凍結
- ✅ 程式碼繼續執行
- ❌ 執行順序不直觀
- ❌ 巢狀 Promise 難以閱讀 (Callback Hell)
Async/Await 的運作原理
範例 3:使用 Async/Await
async function asyncAwaitExample() {
console.log('步驟 1:開始');
// 等待 API 回應,但不阻塞 UI
const data = await fetchDataFromAPI();
console.log('步驟 2:收到資料', data);
console.log('步驟 3:處理完成');
}
asyncAwaitExample();
console.log('步驟 4:後續程式碼');
執行結果
步驟 1:開始
步驟 4:後續程式碼 ← 立即執行,不等待
(1 秒後...)
步驟 2:收到資料 {...}
步驟 3:處理完成
關鍵差異
| 項目 |
同步處理 |
Async/Await |
| UI 狀態 |
凍結 |
可互動 |
| 執行順序 |
嚴格按順序 |
函數內按順序,外部繼續 |
| 程式碼可讀性 |
高 |
高 |
| 效能影響 |
阻塞所有操作 |
不阻塞 |
實際範例比較
情境:從 API 載入使用者資料並顯示
❌ 同步方式(不可行,只是示意)
loadUser() {
console.log('開始載入使用者');
// 假設這會阻塞 3 秒
const user = this.blockingAPICall();
console.log('使用者:', user.name);
console.log('載入完成');
}
// 執行結果:UI 凍結 3 秒,無法互動
✅ 使用 Async/Await(正確方式)
async loadUser() {
console.log('開始載入使用者');
// 等待 API 回應,但 UI 可以互動
const user = await this.httpClient.get('/api/user').toPromise();
console.log('使用者:', user.name);
console.log('載入完成');
}
// 執行結果:UI 不凍結,可以繼續點擊其他按鈕
執行順序視覺化
範例:連續的 API 呼叫
async function multipleAPICalls() {
console.log('1. 開始');
console.log('2. 呼叫 API 1');
const result1 = await api.call1(); // 暫停,等待 1 秒
console.log('3. API 1 完成:', result1);
console.log('4. 呼叫 API 2');
const result2 = await api.call2(); // 暫停,等待 1 秒
console.log('5. API 2 完成:', result2);
console.log('6. 全部完成');
}
multipleAPICalls();
console.log('7. 主程式繼續執行');
時間軸
時間 0ms: 1. 開始
時間 1ms: 2. 呼叫 API 1
時間 2ms: 7. 主程式繼續執行 ← 不等待!
時間 1000ms: 3. API 1 完成
時間 1001ms: 4. 呼叫 API 2
時間 2001ms: 5. API 2 完成
時間 2002ms: 6. 全部完成
關鍵重點
- ⚠️
await 只會暫停**當前 async 函數**的執行
- ✅ **不會暫停**主程式或其他函數
- ✅ UI 事件循環繼續運作
- ✅ 使用者可以繼續操作
Angular 實際應用
範例:從你的程式碼中
// ❌ 如果沒有 async/await(難以閱讀)
loadCustomerGroupData(): void {
this.loadingMaskService.show();
this.ruleSettingService.getKeyGroupByUser(this.getCodeFromUrl())
.subscribe(result => {
const { keyGroup } = result;
this.originData = keyGroup?.map((item: any) => ({
title: `${item}`
})) || [];
this.cardData = JSON.parse(JSON.stringify(this.originData));
this.loadingMaskService.hide();
}, error => {
console.error(error);
this.loadingMaskService.hide();
});
}
// ✅ 使用 async/await(清晰易讀)
async loadCustomerGroupData(): Promise<void> {
try {
this.loadingMaskService.show();
const { keyGroup } = await lastValueFrom(
this.ruleSettingService.getKeyGroupByUser(this.getCodeFromUrl())
);
this.originData = keyGroup?.map((item: any) => ({
title: `${item}`
})) || [];
this.cardData = JSON.parse(JSON.stringify(this.originData));
} catch (error) {
console.error(error);
} finally {
this.loadingMaskService.hide(); // 無論成功或失敗都會執行
}
}
優點
- ✅ 程式碼更容易閱讀和維護
- ✅ 錯誤處理更直觀(try/catch)
- ✅ 使用 finally 確保清理操作
- ✅ 避免 callback hell
常見誤解
❌ 誤解 1:Async/Await 是同步的
// 這不是同步執行!
async function wrong() {
await api.call(); // 不會阻塞 UI
}
-
事實:只是語法看起來像同步,本質仍是非同步
❌ 誤解 2:await 會阻塞整個應用程式
async function example() {
await heavyOperation(); // 只暫停這個函數
}
// UI 和其他函數仍可執行
-
事實:只暫停當前 async 函數,不影響其他程式碼
❌ 誤解 3:不使用 await 就是並行
// 這個範例有歧義,取決於 getUser 和 getPosts 的實作
// 情況 1:如果 getUser/getPosts 是同步函數
function sequential() {
const user = getUser(); // 1 秒 (同步執行)
const posts = getPosts(); // 1 秒 (同步執行)
// 總共 2 秒 - 序列執行
}
// 情況 2:如果 getUser/getPosts 返回 Promise (但沒有 await)
function parallelPromises() {
const user = getUser(); // Promise 立即開始執行
const posts = getPosts(); // Promise 立即開始執行
// 總共 1 秒 - 並行執行,但 user/posts 是 Promise 物件
// 需要額外處理 Promise 結果
}
// 情況 3:正確的序列非同步執行
async function sequentialAsync() {
const user = await getUser(); // 等待 1 秒
const posts = await getPosts(); // 再等待 1 秒
// 總共 2 秒 - 序列執行
}
// 情況 4:正確的並行非同步執行
async function parallelAsync() {
const [user, posts] = await Promise.all([
getUser(), // 同時開始
getPosts() // 同時開始
]);
// 總共 1 秒 - 並行執行
}
補充說明:函數本質決定執行方式
關鍵在於函數的實作方式:
| 函數類型 |
執行方式 |
說明 |
| 同步函數 |
序列執行 |
getUser() 完成後才執行 getPosts() |
| Promise 函數 (無 await) |
並行執行 |
兩個 Promise 同時開始,但結果是 Promise 物件 |
| Promise 函數 (有 await) |
序列執行 |
await getUser() 完成後才執行 await getPosts() |
| Promise.all |
並行執行 |
多個 Promise 同時執行,等待全部完成 |
實際範例:
// 假設 getUser 和 getPosts 都是非同步函數
// ❌ 錯誤理解:以為是序列,但實際是並行
function wrongSequential() {
const user = getUser(); // Promise 開始執行
const posts = getPosts(); // Promise 也開始執行
// user 和 posts 都是 Promise,需要額外處理
}
// ✅ 正確的序列執行
async function correctSequential() {
const user = await getUser(); // 等待完成
const posts = await getPosts(); // 再等待完成
}
// ✅ 正確的並行執行
async function correctParallel() {
const [user, posts] = await Promise.all([
getUser(),
getPosts()
]);
}
最佳實踐
1. 何時使用 Async/Await
// ✅ 適合:API 呼叫
async loadData() {
const data = await this.http.get('/api/data').toPromise();
return data;
}
// ✅ 適合:檔案讀取
async readFile() {
const content = await fs.readFile('file.txt');
return content;
}
// ❌ 不適合:簡單計算
async calculate() {
const result = await (2 + 2); // 不需要 await
return result;
}
2. 錯誤處理
// ✅ 正確:使用 try/catch
async function correctErrorHandling() {
try {
const data = await api.call();
return data;
} catch (error) {
console.error('API 錯誤:', error);
throw error; // 或處理錯誤
} finally {
// 清理操作
}
}
3. 並行處理
// ❌ 序列執行(較慢)
async function sequential() {
const user = await getUser(); // 1 秒
const posts = await getPosts(); // 1 秒
// 總共 2 秒
}
// ✅ 並行執行(較快)
async function parallel() {
const [user, posts] = await Promise.all([
getUser(),
getPosts()
]);
// 總共 1 秒(同時執行)
}
4. Angular 中的使用
// ✅ 在 ngOnInit 中使用
async ngOnInit() {
await this.loadData();
}
// ✅ 在事件處理中使用
async onSubmit() {
try {
await this.saveData();
this.showSuccessMessage();
} catch (error) {
this.showErrorMessage();
}
}
// ⚠️ 記得處理錯誤
async riskyOperation() {
try {
await this.dangerousAPI();
} catch (error) {
// 必須處理錯誤
console.error(error);
}
}
總結
| 特性 |
同步處理 |
Async/Await |
| 執行方式 |
逐行阻塞執行 |
非同步,不阻塞 |
| UI 狀態 |
凍結 |
可互動 |
| 程式碼可讀性 |
高 |
高 |
| 效能 |
差(阻塞) |
好(不阻塞) |
| 錯誤處理 |
try/catch |
try/catch |
| 使用場景 |
簡單計算 |
API、I/O 操作 |
關鍵要點
- ✅ Async/Await 是非同步的,只是看起來像同步
- ✅ 不會阻塞 UI,使用者可以繼續操作
- ✅ 提高可讀性,避免 callback hell
- ✅ 適合 I/O 操作,如 API 呼叫、檔案讀取
- ⚠️ 記得錯誤處理,使用 try/catch
- ⚠️ 考慮並行,使用 Promise.all 提升效能
參考資源