現在42Tokyo正在進行最後的團隊課題(Web應用程式開發),雖然在工作中也經常用JavaScript的async/await來寫程式,但不禁懷疑為什麼要這樣寫?因此,我回顧了非同步處理的歷史。
※本文是從JS的角度寫的,請多多包涵。。。
在JavaScript中,如何處理耗時的操作(如API通訊或計時器等)始終是一個巨大挑戰。讓我們回顧這個演變的歷史,並與代碼的發展相結合。
| 世代 | 手法 | 進化的重點 |
|---|---|---|
| 第1世代 | 回呼函數 | 所有的操作都需手動管理(容易出錯) |
| 第2世代 | Promise (ES6/2015) | 能將非同步處理當作「物件」來處理,並實現標準化 |
| 第3世代 | async/await (ES2017) | 基於Promise,進一步使「外觀」接近同步處理的完成形態 |
在早期的JavaScript中,傳遞一個「完成後要執行的函數(回呼函數)」作為參數,是唯一的方法。
特徵
// 深入的嵌套
getData(function(a) {
getMoreData(a, function(b) {
getEvenMoreData(b, function(c) {
console.log(c);
}, failureCallback);
}, failureCallback);
}, failureCallback);
為了解決回呼函數的缺點,「承諾未來結果的物件」Promise出現了。
特徵
// 垂直流動的「鏈式」結構
getData()
.then(a => getMoreData(a))
.then(b => getEvenMoreData(b))
.then(c => console.log(c))
.catch(error => console.error(error)); // ← 這一行可以捕捉所有步驟的失敗!
| 比較項目 | C# | JavaScript |
|---|---|---|
| 返回值類型 | Task 或 Task<T> |
Promise |
| 關鍵字 | async / await |
async / await |
| 錯誤處理 | 可以使用 try-catch |
可以使用 try-catch |
這兩種語言的編譯器或引擎在背景中創建狀態機(State Machine),以控制程式的暫停和繼續,這樣就可以在不阻塞線程(在JS的情況下是主線程)的情況下進行等待。
特徵
async function executeTasks() {
try {
const result = await doSomething();
const newResult = await doSomethingElse(result);
const finalResult = await doThirdThing(newResult);
console.log(`最終結果: ${finalResult}`);
} catch (error) {
// 在任何 await 失敗時都會到這裡
failureCallback(error);
}
}
如今,除非有特殊原因,否則不會再直接撰寫回呼函數,而是「以async/await的方式智能地調用返回Promise的函數」成為了標準做法。
| 時代 | 手法 | 主要解決方案 |
|---|---|---|
| ~2014年 | 回呼函數 | 非同步處理的基礎,但嵌套太深,難以控制。 |
| 2015年~ | Promise | Promise明確解決了問題,能作為物件處理結果。 |
| 2017年~ | async/await | 解決了「可讀性」問題,可以像同步處理一樣書寫。 |
老實說,在我進行調查之前,我並沒有深入思考非同步處理為何要用async/await來寫,但調查之後,我理解了回呼地獄的原因以及為什麼async/await是更優越的選擇!
此外,我也希望能多了解Promise物件,等到有內容整理好後,我會寫一篇相關的文章!
非常感謝您閱讀到最後!💪
參考資料
MDN - Promise的使用
MDN - async function