前幾天學的東西都算是「同步」的程式碼,執行順序一行一行跑。
但在現實裡,很多情況需要處理「非同步」:例如去抓 API、等資料庫回應、等使用者操作。
今天就來挑戰 JavaScript 的非同步寫法:從 Callback 到 Promise,再到 async/await。
想像我去餐廳點餐:
最早的非同步寫法是「把一個函式丟進去,等完成後再呼叫」:
function getData(callback) {
setTimeout(() => {
callback("資料拿到了!");
}, 1000);
}
getData(result => {
console.log(result);
});
缺點是:如果有很多層,就會變成「回呼地獄」(callback hell)。
Promise 可以想像是一張「承諾卡片」,有三種狀態:
pending
(進行中)fulfilled
(成功)rejected
(失敗)let p = new Promise((resolve, reject) => {
setTimeout(() => resolve("資料拿到了!"), 1000);
});
p.then(result => console.log(result))
.catch(err => console.error(err));
這樣寫比 callback 清楚,但 then
一直串下去還是有點亂。
這是 ES8 加入的語法,可以把非同步程式寫得像同步一樣:
function getData() {
return new Promise(resolve => {
setTimeout(() => resolve("資料拿到了!"), 1000);
});
}
async function main() {
try {
let result = await getData();
console.log(result);
} catch (err) {
console.error("發生錯誤", err);
}
}
main();
這就是目前最推薦的寫法。
瀏覽器和 Node.js 都提供 fetch
,可以直接去抓 API。
今天用 JSONPlaceholder,這個免費測試 API。
async function fetchUsers() {
try {
let response = await fetch("https://jsonplaceholder.typicode.com/users");
let users = await response.json();
console.log("=== 使用者清單 ===");
users.forEach(user => {
console.log(`${user.id}. ${user.name} (${user.email})`);
});
} catch (error) {
console.error("抓資料失敗:", error);
}
}
fetchUsers();
輸出結果(截取部分):
=== 使用者清單 ===
1. Leanne Graham (Sincere@april.biz)
2. Ervin Howell (Shanna@melissa.tv)
3. Clementine Bauch (Nathan@yesenia.net)
...
今天一開始看到「callback → promise → async/await」覺得有點複雜,
但用餐廳點餐的比喻來想,就比較能理解:
非同步的重點就是「我可以先做別的事,等資料好再處理」。
最大的收穫是:
callback
容易變成巢狀地獄promise
清楚一些,但 then 串多了也會亂async
/ await
最直覺,跟同步程式幾乎一樣最後用 fetch
抓 API
的練習,第一次感受到「原來我也能拿到網路上的資料」,
看到清單跑出來的那一刻,真的蠻有成就感的 🎉