Web前端入門第 79 問:JavaScript async & await 的異步任務進化之路
JS 中異步任務隨處可見,比如:
1、用戶交互的點擊、輸入
2、網絡請求的 fetch、ajax、WebSocket
3、資源中的圖片、腳本加載
4、定時任務 setTimeout、setInterval、動畫
5、Web Worker 中的后臺任務
以上這些地方都能見到 JS 異步任務使用場景。
不過 JS 的異步任務 使用方法 卻經過了多次迭代,多次進化才像一個完全體~~
回調方法
最原始的使用方法,目前也還能在各種鉤子函數中見到 回調函數 的身影。
function asyncTask(callback) {
console.log('開始執行異步任務');
setTimeout(() => {
console.log('異步任務執行完畢');
callback && callback();
}, 1000);
}
// 傳入匿名函數用于回調方法
asyncTask(() => {
console.log('異步任務執行完畢,回調函數執行完畢');
});
以上 asyncTask 傳入的 匿名函數 便是回調方法(也稱為回調函數),回調方法將會在等到 setTimeout 執行完畢時執行。
Promise
在使用回到函數時,容易陷入回調地獄,而 Promise 的出現便是為了解決回調地獄問題。
使用回調函數嵌套太多時,就會有像套娃一樣的代碼,比如:
a(() => {
b(() => {
c(() => {
d(() => {})
})
})
})
使用 Promise 優化之后可以是這樣:
a()
.then(() => {
return b()
}).then(() => {
return c()
}).then(() => {
return d()
})
最開始的 setTimeout 函數使用 Promise 優化之后:
function asyncTask() {
return new Promise((resolve) => {
console.log('開始執行異步任務');
setTimeout(() => {
console.log('異步任務執行完畢');
resolve();
}, 1000);
});
}
// Promise 鏈式調用
asyncTask().then(() => {
console.log('異步任務執行完畢,回調函數執行完畢');
});
關于 Promise 可參考之前的文章:Web前端入門第 69 問:JavaScript Promise 提供的方法都使用過嗎?
async & await
使用 Promise 的鏈式調用確實大大的改善了回調地獄,但還是繞不過代碼不太優雅的問題,于是乎 JS 標準定制的那群大佬,就在 ES2017(ES8) 中引入了 async 和 await 關鍵字,由于優化 JS 中的異步邏輯,使得代碼就像同步任務一樣。
async & await 僅僅是 Promise 的語法糖,所以它倆基本是與 Promise 深度綁定~~
改寫上面的 Promise 示例:
(async () => {
function asyncTask() {
return new Promise((resolve) => {
console.log('開始執行異步任務');
setTimeout(() => {
console.log('異步任務執行完畢');
resolve();
}, 1000);
});
}
// await 調用
await asyncTask()
console.log('異步任務執行完畢,回調函數執行完畢');
})()
await 關鍵字用于等待一個 Promise 任務完成,然后繼續執行后續的代碼。
注意:在使用 await 關鍵字時,必須在外層作用域的函數身上加上 async 關鍵字,否則會報錯。
頂層 await
在 ES2023 發布后,異步任務又被革命了,在 ES 模塊 中,允許在頂層作用域使用 await 關鍵字而不必再套在 async 函數中。
<script type="module">
function asyncTask() {
return new Promise((resolve) => {
console.log('開始執行異步任務');
setTimeout(() => {
console.log('異步任務執行完畢');
resolve();
}, 1000);
});
}
// await 調用
await asyncTask()
console.log('異步任務執行完畢,回調函數執行完畢');
</script>
注意上面的 type="module",表示使用 ES 模塊語法,這種語法 Chrome 61 版本開始支持(2017年后)。
如果沒有 type="module",表示使用正常的 script 執行腳本,上面的代碼會報錯:
Uncaught SyntaxError: await is only valid in async functions and the top level bodies of modules
表示 await 必須在 async 函數中使用,在頂層使用時候必須放在 ES 模塊中。
返回內容
await 關鍵字可以等待一個 Promise resolve 方法返回值:
(async () => {
function asyncTask() {
return new Promise((resolve) => {
console.log('開始執行異步任務');
setTimeout(() => {
console.log('異步任務執行完畢');
resolve({ name: '前端路引' });
}, 1000);
});
}
// await 調用
const res = await asyncTask()
console.log('異步任務執行完畢,返回內容:', res);
// 輸出 {name: '前端路引'}
})()
生成器函數: async 規范落地之前,JS 還有過一個 生成器函數 也能用來處理異步任務,不過在實際開發中很少使用,在一些多任務的腳手架里面能看到它的身影,使用方法可參考之前的文章:
Web前端入門第 64 問:JavaScript 幾種函數定義方式有什么區別?
寫在最后
JS 的任務調度機制讓它擁有大量的異步編程,各式各樣的使用方式都有必要了解學習,要不然...嘿嘿...大佬寫的代碼看不懂~~

浙公網安備 33010602011771號