JavaScript 非同步處理方式
JavaScript 提供了多種處理非同步操作的方式,包括 Callback、Promise 和 Async/Await。每種方式都有其特點和適用場景。
Callback Function
Callback Function(回調函數) 是 JavaScript 中處理非同步操作最傳統的方式。它是一個作為參數傳遞給另一個函數的函數,當特定 事件發生或操作完成時會被調用。
基本概念:
-
什麼是 Callback:
- 一個函數作為參數傳遞給另一個函數
- 在特定條件滿足時被調用
- 常用於處理非同步操作
-
基本語法:
function doSomething(callback) {
// 執行某些操作
const result = "操作完成";
callback(result); // 調用回調函數
}
doSomething(function (result) {
console.log(result); // "操作完成"
}); -
常見應用場景:
- 事件處理
- 定時器
- AJAX 請求
- 文件讀取
Callback 的優缺點:
優點:
- 簡單直觀
- 廣泛支援
- 不需要額外的語法
缺點:
- 容易產生「回調地獄」(Callback Hell)
- 錯誤處理困難
- 代碼可讀性差
回調地獄問題:
// 回調地獄示例
getData(function (a) {
getMoreData(a, function (b) {
getEvenMoreData(b, function (c) {
getFinalData(c, function (d) {
console.log(d);
});
});
});
});
Promise
Promise 本身是同步函數。在 JavaScript 中,當你創建一個 Promise 物件(例如使用 new Promise()),這是一個同步的操作。Promise 的構造函數會立即執行,並且傳入的執行器函數(executor function,即 (resolve, reject) => { ... })也會同步執行。
詳細說明:
-
Promise 構造函數是同步的: 當你執行
new Promise((resolve, reject) => { ... })時,執行器函數會立即運行,而不是等到事件循環的下一個 tick。這意味著執行器內的同步代碼會在當前調用棧中立即執行。console.log("開始");
const promise = new Promise((resolve, reject) => {
console.log("執行器 內部");
resolve("成功");
});
console.log("結束");輸出結果:
開始
執行器內部
結束可以看到,
執行器內部的日誌會在結束之前打印,證明執行器是同步運行的。 -
Promise 的狀態變化和回調是非同步的: 雖然
Promise的創建和執行器是同步的,但當resolve或reject被調用時,與之相關的.then()、.catch()或.finally()回調函數會被放入微任務隊列(microtask queue),並在當前調用棧清空後(即同步代碼執行完畢後)才會執行。這使得 Promise 的回調處理是非同步的。console.log("開始");
const promise = new Promise((resolve, reject) => {
console.log("執行器內部");
resolve("成功");
});
promise.then((result) => {
console.log("then 回調:", result);
});
console.log("結束");輸出結果:
開始
執行器內部
結束
then 回調: 成功這裡可以看到,
.then的回調是在同步代碼(開始和結束)執行完後才執行的,這是因為.then的處理被推遲到微任務隊列。 -
總結:
Promise的創建和執行器函數是同步的。Promise的狀態變更(resolve或reject)觸發的回調(如.then、.catch)是非同步的,會在微任務隊列中執行。- 這種設計使得
Promise能夠在同步代碼中初始化,但其結果處理是非同步的,符合 JavaScript 的事件循環模型。
Async/Await
Async/Await 是 ES2017 引入的語法糖,讓非同步代碼看起來更像同步代碼,是處理 Promise 的更優雅方式。
基本概念:
-
async 函數:
- 宣告一個函數為非同步函數
- 自動返回一個 Promise
- 可以使用 await 關鍵字
-
await 關鍵字:
- 只能在 async 函數內使用
- 等待 Promise 解決
- 暫停函數執行直到 Promise 完成
基本語法:
// 宣告 async 函數
async function fetchData() {
try {
// 使用 await 等待 Promise
const response = await fetch("https://api.example.com/data");
const data = await response.json();
return data;
} catch (error) {
console.error("錯誤:", error);
}
}
// 調用 async 函數
fetchData().then((data) => {
console.log(data);
});
Async/Await 的優缺點:
優點:
- 代碼更易讀,類似同步代碼
- 錯誤處理更簡單(使用 try/catch)
- 避免回調地獄
- 更好的調試體驗
缺點:
- 只能在 async 函數內使用
- 可能讓代碼看起來是同步的,但實際上是非同步的
- 需要較新的 JavaScript 環境支援
三種方式的比較
讓我用同一個例子來展示 Callback、Promise 和 Async/Await 的差異:
1. Callback 方式:
function fetchUserData(userId, callback) {
setTimeout(() => {
callback(null, { id: userId, name: "張三" });
}, 1000);
}
fetchUserData(1, (error, user) => {
if (error) {
console.error(error);
} else {
console.log(user);
}
});
2. Promise 方式:
function fetchUserData(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ id: userId, name: "張三" });
}, 1000);
});
}
fetchUserData(1)
.then((user) => console.log(user))
.catch((error) => console.error(error));
3. Async/Await 方式:
function fetchUserData(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ id: userId, name: "張三" });
}, 1000);
});
}
async function getUser() {
try {
const user = await fetchUserData(1);
console.log(user);
} catch (error) {
console.error(error);
}
}
getUser();
比較總結:
| 特性 | Callback | Promise | Async/Await |
|---|---|---|---|
| 可讀性 | 差(回調地獄) | 中等 | 好(類似同步代碼) |
| 錯誤處理 | 困難 | 中等 | 簡單(try/catch) |
| 支援度 | 廣泛 | 廣泛 | 需要較新環境 |
| 學習曲線 | 簡單 | 中等 | 中等 |
實際應用建議
- 現代開發:推薦使用 Async/Await,代碼更清晰易讀
- 舊專案維護:可能需要使用 Callback 或 Promise
- API 設計:可以返回 Promise,讓使用者選擇使用
.then()或await - 錯誤處理:Async/Await 配合 try/catch 是最直觀的方式