非同步(Asynchronous)是程式設計中不阻塞主執行緒、允許程式在等待 I/O 操作(如網路請求、檔案讀取、資料庫查詢)時繼續執行其他任務的程式模型。它透過回調、Promise 或 async/await 等機制實現,讓單執行緒語言也能高效處理並發,提升系統吞吐量與響應速度,特別適合 Web 開發與伺服器應用。
同步 vs 非同步的核心差異
同步程式像排隊買票,一人完成才能輪到下一個,遇到等待就全體停滯:
print("開始請求")
response = requests.get("https://api.example.com") # 阻塞 2 秒
print("請求完成") # 2 秒後才執行
# 總耗時:2 秒
非同步程式像多工處理,發起請求後立即做其他事,完成時通知:
print("開始請求")
response = await requests.get("https://api.example.com") # 不阻塞
print("處理其他任務")
print("請求完成") # 幾乎立即執行
# 總耗時:< 0.1 秒
非同步的實現機制
1. 回調函式(Callback)
最早方式,操作完成時呼叫指定函式:
// 舊式 Node.js
getUser(123, function(user) {
console.log(user.name); // 回調執行
});
console.log("繼續執行"); // 先執行
缺點:回調地獄(Callback Hell),巢狀過深難維護。
2. Promise
封裝非同步結果,支援鏈式處理:
getUser(123)
.then(user => console.log(user.name))
.catch(err => console.error(err));
3. async/await(現代標準)
語法糖,讓非同步程式碼像同步般可讀:
async function fetchUser() {
try {
const user = await getUser(123); // 暫停等待
console.log(user.name);
} catch (err) {
console.error(err);
}
}
語言別非同步實現
| 語言 | 機制 | 範例語法 |
|---|---|---|
| JavaScript | Event Loop + Promise | async/await、fetch() |
| Python | asyncio | async def、await、aiohttp |
| C# | Task Parallel Library | async Task、await |
| Go | Goroutines + Channel | go func()、<-ch |
| Java | CompletableFuture | supplyAsync().thenApply() |
Python asyncio 範例:
import asyncio
import aiohttp
async def fetch_user(session, user_id):
async with session.get(f'https://api.example.com/users/{user_id}') as resp:
return await resp.json()
async def main():
async with aiohttp.ClientSession() as session:
tasks = [fetch_user(session, i) for i in range(1, 6)]
users = await asyncio.gather(*tasks) # 並行執行
print(users)
asyncio.run(main())
非同步的執行原理
事件迴圈(Event Loop)是核心,管理任務佇列:
1. 註冊非同步任務 → 事件迴圈佇列
2. 主執行緒執行其他程式碼
3. 任務完成 → 事件迴圈調度回調
4. 重複 1-3
JavaScript 事件迴圈簡圖:
任務佇列: [setTimeout CB, fetch CB, UI render]
↑
Event Loop ← 主執行緒
實際應用場景
1. Web API 伺服器
// Express 傳統(阻塞)
app.get('/users', async (req, res) => {
const users = await db.query('SELECT * FROM users'); // 阻塞
res.json(users);
});
// 非同步優化(Node.js)
app.get('/users', async (req, res) => {
const usersTask = db.query('SELECT * FROM users');
const statsTask = db.query('SELECT COUNT(*) FROM stats');
const [users, stats] = await Promise.all([usersTask, statsTask]);
res.json({ users, stats }); // 並行查詢,速度 x2
});
2. 前端使用者體驗
// 並行載入,提升首屏速度
const [user, posts, comments] = await Promise.all([
fetchUser(),
fetchPosts(),
fetchComments()
]);
非同步的優點與挑戰
優點:
-
高吞吐量:單執行緒處理萬級並發
-
低延遲:不阻塞 UI,主執行緒保持響應
-
資源效率:避免執行緒切換開銷
挑戰:
-
複雜性:錯誤處理、狀態管理困難
-
除錯困難:執行順序非線性
-
資源洩漏:未取消請求浪費記憶體
最佳實務與陷阱避免
正確寫法:
async function processUsers() {
try {
const users = await fetchUsers();
return users.map(formatUser);
} catch (error) {
console.error('獲取使用者失敗:', error);
return []; // 優雅降級
}
}
常見錯誤:
// 忘記 await
const users = fetchUsers(); // Promise 未解析
console.log(users[0].name); // undefined
// 未處理錯誤
fetchUsers().then(users => {
// error 被吞沒
});
效能技巧:
-
Promise.all()並行執行 -
Promise.allSettled()部分失敗仍繼續 -
設定超時
Promise.race([task, timeout(5000)]) -
取消機制
AbortController
非同步是現代 Web 開發的基石,從 Node.js 單執行緒神話到 Python asyncio,從前端 SPA 到微服務 API。掌握 async/await 與事件迴圈原理,就能打造高效、響應迅速的應用,成為全端開發的必備技能。