深入理解JavaScript的async/await
async 和 await 在干什么
任意一個(gè)名稱都是有意義的,先從字面意思來(lái)理解。async 是“異步”的簡(jiǎn)寫,而 await 的意思是等待。所以應(yīng)該很好理解 async 用于申明一個(gè) function 是異步的,而 await 等待某個(gè)操作完成。
那么async/await到底是干嘛的呢?我們先來(lái)簡(jiǎn)單介紹一下。
- async/await 是一種編寫異步代碼的新方法。之前異步代碼的方案是回調(diào)和 promise。
- async/await 是建立在 promise 的基礎(chǔ)上。(對(duì)promise不熟悉的同學(xué)可以看一下這篇文章入門Promise的正確姿勢(shì))
- async/await 像 promise 一樣,也是非阻塞的。
- async/await 讓異步代碼看起來(lái)、表現(xiàn)起來(lái)更像同步代碼。這正是其威力所在。
async 起什么作用
這個(gè)問(wèn)題的關(guān)鍵在于,async 函數(shù)是怎么處理它的返回值的!
我們當(dāng)然希望它能直接通過(guò) return 語(yǔ)句返回我們想要的值,但是如果真是這樣,似乎就沒(méi) await 什么事了。所以,寫段代碼來(lái)試試,看它到底會(huì)返回什么:
<script>
async function test(){
return 'hello async';
}
let result = test();
console.log(result);
</script>
看到輸出就恍然大悟了——輸出的是一個(gè) Promise 對(duì)象。
Promise {<resolved>: "hello async"}
所以,async 函數(shù)返回的是一個(gè) Promise 對(duì)象。async 函數(shù)(包含函數(shù)語(yǔ)句、函數(shù)表達(dá)式、Lambda表達(dá)式)會(huì)返回一個(gè) Promise 對(duì)象,如果在函數(shù)中 return 一個(gè)直接量,async 會(huì)把這個(gè)直接量通過(guò) Promise.resolve() 封裝成 Promise 對(duì)象。
async 函數(shù)返回的是一個(gè) Promise 對(duì)象,所以在最外層不能用 await 獲取其返回值的情況下,我們當(dāng)然應(yīng)該用原來(lái)的方式:then() 鏈來(lái)處理這個(gè) Promise 對(duì)象,就像這樣
async function test(){
return 'hello async';
}
test().then((val) => {
console.log(val); //hello async
})
現(xiàn)在回過(guò)頭來(lái)想下,如果 async 函數(shù)沒(méi)有返回值,又該如何?很容易想到,它會(huì)返回 Promise.resolve(undefined)。
聯(lián)想一下 Promise 的特點(diǎn)——無(wú)等待,所以在沒(méi)有 await 的情況下執(zhí)行 async 函數(shù),它會(huì)立即執(zhí)行,返回一個(gè) Promise 對(duì)象,并且,絕不會(huì)阻塞后面的語(yǔ)句。這和普通返回 Promise 對(duì)象的函數(shù)并無(wú)二致。
那么下一個(gè)關(guān)鍵點(diǎn)就在于 await 關(guān)鍵字了。
await 到底在等啥
一般來(lái)說(shuō),都認(rèn)為 await 是在等待一個(gè) async 函數(shù)完成。不過(guò)按語(yǔ)法說(shuō)明,await 等待的是一個(gè)表達(dá)式,這個(gè)表達(dá)式的計(jì)算結(jié)果是 Promise 對(duì)象或者其它值(換句話說(shuō),就是沒(méi)有特殊限定)。
因?yàn)?async 函數(shù)返回一個(gè) Promise 對(duì)象,所以 await 可以用于等待一個(gè) async 函數(shù)的返回值——這也可以說(shuō)是 await 在等 async 函數(shù),但要清楚,它等的實(shí)際是一個(gè)返回值。注意到 await 不僅僅用于等 Promise 對(duì)象,它可以等任意表達(dá)式的結(jié)果,所以,await 后面實(shí)際是可以接普通函數(shù)調(diào)用或者直接量的。所以下面這個(gè)示例完全可以正確運(yùn)行。
function getSomething(){
return "something";
}
async function testAsync(){
return Promise.resolve('hello async');
}
async function test(){
let v1 = await getSomething();
let v2 = await testAsync();
console.log(v1,v2);
}
test();
console.log('我執(zhí)行了');
//執(zhí)行結(jié)果為:
//我執(zhí)行了
//something,hello async
await 等到了要等的,然后呢
await 等到了它要等的東西,一個(gè) Promise 對(duì)象,或者其它值,然后呢?我不得不先說(shuō),await 是個(gè)運(yùn)算符,用于組成表達(dá)式,await 表達(dá)式的運(yùn)算結(jié)果取決于它等的東西。
如果它等到的不是一個(gè) Promise 對(duì)象,那 await 表達(dá)式的運(yùn)算結(jié)果就是它等到的東西。
如果它等到的是一個(gè) Promise 對(duì)象,await 就忙起來(lái)了,它會(huì)阻塞后面的代碼,等著 Promise 對(duì)象 resolve,然后得到 resolve 的值,作為 await 表達(dá)式的運(yùn)算結(jié)果。
看到上面的阻塞一詞,心慌了吧……放心,這就是 await 必須用在 async 函數(shù)中的原因。async 函數(shù)調(diào)用不會(huì)造成阻塞(也就是第13行代碼不會(huì)被阻塞),它內(nèi)部所有的阻塞都被封裝在一個(gè) Promise 對(duì)象中異步執(zhí)行。
async/await 幫我們干了啥
作個(gè)簡(jiǎn)單的比較
上面已經(jīng)說(shuō)明了 async 會(huì)將其后的函數(shù)(函數(shù)表達(dá)式或 Lambda)的返回值封裝成一個(gè) Promise 對(duì)象,而 await 會(huì)等待這個(gè) Promise 完成,并將其 resolve 的結(jié)果返回出來(lái)。
現(xiàn)在舉例,用 setTimeout 模擬耗時(shí)的異步操作,先來(lái)看看不用 async/await 會(huì)怎么寫。
function takeLongTime(){
return new Promise((resolve) => {
setTimeout(() => resolve('long time value'),1000);
})
}
takeLongTime().then((v) => {
console.log('get:',v);
})
如果改用 async/await 呢,會(huì)是這樣。
function takeLongTime(){
return new Promise((resolve) => {
setTimeout(() => resolve('long time value'),1000);
})
}
async function test(){
let v = await takeLongTime();//等待異步操作的結(jié)果,阻塞后面代碼的執(zhí)行
console.log(v);
}
眼尖的同學(xué)已經(jīng)發(fā)現(xiàn) takeLongTime() 沒(méi)有申明為 async。實(shí)際上,takeLongTime() 本身就是返回的 Promise 對(duì)象,加不加 async結(jié)果都一樣,如果沒(méi)明白,請(qǐng)回過(guò)頭再去看看上面的“async 起什么作用”。
又一個(gè)疑問(wèn)產(chǎn)生了,這兩段代碼,兩種方式對(duì)異步調(diào)用的處理(實(shí)際就是對(duì) Promise 對(duì)象的處理)差別并不明顯,甚至使用 async/await 還需要多寫一些代碼,那它的優(yōu)勢(shì)到底在哪?
async/await 的優(yōu)勢(shì)在于處理 then 鏈
單一的 Promise 鏈并不能發(fā)現(xiàn) async/await 的優(yōu)勢(shì),但是,如果需要處理由多個(gè) Promise 組成的 then 鏈的時(shí)候,優(yōu)勢(shì)就能體現(xiàn)出來(lái)了(很有意思,Promise 通過(guò) then 鏈來(lái)解決多層回調(diào)的問(wèn)題,現(xiàn)在又用 async/await 來(lái)進(jìn)一步優(yōu)化它)。
假設(shè)一個(gè)業(yè)務(wù),分多個(gè)步驟完成,每個(gè)步驟都是異步的,而且依賴于上一個(gè)步驟的結(jié)果。我們?nèi)匀挥?setTimeout 來(lái)模擬異步操作:
/*
* 傳入?yún)?shù)n,表示這個(gè)函數(shù)執(zhí)行的時(shí)間(毫秒)
* 執(zhí)行的結(jié)果是 n+200,這個(gè)值將用于下一步驟
*/
function takeLongTime(n){
return new Promise((resolve) => {
setTimeout(() => resolve(n + 200),n);
})
}
function step1(n){
console.log(`step1 with ${n}`);
return takeLongTime(n);
}
function step2(n){
console.log(`step2 with ${n}`);
return takeLongTime(n);
}
function step3(n){
console.log(`step3 with ${n}`);
return takeLongTime(n);
}
現(xiàn)在用 Promise 方式來(lái)實(shí)現(xiàn)這三個(gè)步驟的處理。
function doIt(){
console.time('doIt');
let time1 = 300;
step1(time1)
.then((time2) => step2(time2))
.then((time3) => step3(time3))
.then((result) => {
console.log(`result is ${result}`);
console.timeEnd("doIt");
})
}
doIt();
//執(zhí)行結(jié)果為:
//step1 with 300
//step2 with 500
//step3 with 700
//result is 900
//doIt: 1510.2490234375ms
輸出結(jié)果 result 是 step3() 的參數(shù) 700 + 200 = 900。doIt() 順序執(zhí)行了三個(gè)步驟,一共用了 300 + 500 + 700 = 1500 毫秒,和 console.time()/console.timeEnd() 計(jì)算的結(jié)果一致。
如果用 async/await 來(lái)實(shí)現(xiàn)呢,會(huì)是這樣。
async function doIt() {
console.time('doIt');
let time1 = 300;
let time2 = await step1(time1);//將Promise對(duì)象resolve(n+200)的值賦給time2
let time3 = await step1(time2);
let result = await step1(time3);
console.log(`result is ${result}`);
console.timeEnd('doIt');
}
doIt();
//執(zhí)行結(jié)果為:
//step1 with 300
//step2 with 500
//step3 with 700
//result is 900
//doIt: 1512.904296875ms
結(jié)果和之前的 Promise 實(shí)現(xiàn)是一樣的,但是這個(gè)代碼看起來(lái)是不是清晰得多,幾乎跟同步代碼一樣。
還有更酷的
現(xiàn)在把業(yè)務(wù)要求改一下,仍然是三個(gè)步驟,但每一個(gè)步驟都需要之前每個(gè)步驟的結(jié)果。
/*
* 傳入?yún)?shù)n,表示這個(gè)函數(shù)執(zhí)行的時(shí)間(毫秒)
* 執(zhí)行的結(jié)果是 n+200,這個(gè)值將用于下一步驟
*/
function takeLongTime(n){
return new Promise((resolve) => {
setTimeout(() => resolve(n + 200),n);
})
}
function step1(n){
console.log(`step1 with ${n}`);
return takeLongTime(n);
}
function step2(m,n){
console.log(`step2 with ${m} + ${n}`);
return takeLongTime(m + n);
}
function step3(k,m,n){
console.log(`step3 with ${k} + ${m} + ${n}`);
return takeLongTime(k + m + n);
}
這回先用 async/await 來(lái)寫:
async function doIt() {
console.time('doIt');
let time1 = 300;
let time2 = await step1(time1);//將Promise對(duì)象resolve(n+200)的值賦給time2
let time3 = await step2(time2,time1);
let result = await step3(time3,time2,time1);
console.log(`result is ${result}`);
console.timeEnd('doIt');
}
doIt();
//執(zhí)行結(jié)果為:
//step1 with 300
//step2 with 500 + 300
//step3 with 1000 + 500 + 300
//result is 2000
//doIt: 2916.655029296875ms
除了覺得執(zhí)行時(shí)間變長(zhǎng)了之外,似乎和之前的示例沒(méi)啥區(qū)別啊!別急,認(rèn)真想想如果把它寫成 Promise 方式實(shí)現(xiàn)會(huì)是什么樣子?
function doIt() {
console.time('doIt');
let time1 = 300;
step1(time1)
.then((time2) => {
return step2(time1,time2)
.then((time3) => [time1,time2,time3])//step3需要用到time1,time2,time3,因此需要返回
})
.then((times) => {
let [time1,time2,time3] = times;
return step3(time1,time2,time3)
})
.then((result) => {
console.log(`result is ${result}`);
console.timeEnd('doIt');
})
}
doIt();
//執(zhí)行結(jié)果為:
//step1 with 300
//step2 with 300 + 500
//step3 with 300 + 500 + 1000
//result is 2000
//doIt: 2919.49609375ms
有沒(méi)有感覺有點(diǎn)復(fù)雜的樣子?那一堆參數(shù)處理,就是 Promise 方案的死穴—— 參數(shù)傳遞太麻煩了,看著就暈!
注意點(diǎn)
就目前來(lái)說(shuō),已經(jīng)理解 async/await 了吧?但其實(shí)還有一些事情沒(méi)提及——Promise 有可能 reject 啊,怎么處理呢?
await 命令后面的 Promise 對(duì)象,運(yùn)行結(jié)果可能是 rejected,所以最好把 await 命令放在 try...catch 代碼塊中。
async function myFunction() {
try {
await somethingThatReturnAPromise();
} catch (err){
console.log(err);
}
}
//另一種寫法
async function myFunction() {
await somethingThatReturnAPromise().catch(function(err) {
console.log(err);
})
}
- Js中async/await的執(zhí)行順序詳解
- JS中async/await實(shí)現(xiàn)異步調(diào)用的方法
- JavaScript中的await/async的作用和用法
- 在JS循環(huán)中使用async/await的方法
- JS為什么說(shuō)async/await是generator的語(yǔ)法糖詳解
- JS基于ES6新特性async await進(jìn)行異步處理操作示例
- JavaScript async/await原理及實(shí)例解析
- 詳解JavaScript Promise和Async/Await
- 深入淺出探究JavaScript中的async與await
- JS中如何優(yōu)雅的使用async await詳解
相關(guān)文章
javascript fullscreen全屏實(shí)現(xiàn)代碼
用了實(shí)現(xiàn)打開一個(gè)滿屏的代碼2009-04-04
js事件源window.event.srcElement兼容性寫法(詳解)
下面小編就為大家?guī)?lái)一篇js事件源window.event.srcElement兼容性寫法(詳解)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-11-11
驚云JS隨機(jī)排序程序隨機(jī)顯示信息-每次新聞顯示順序都不一樣
驚云JS隨機(jī)排序程序隨機(jī)顯示信息-每次新聞顯示順序都不一樣...2007-11-11
Bootstrap響應(yīng)式側(cè)邊欄改進(jìn)版
這篇文章主要為大家詳細(xì)介紹了Bootstrap響應(yīng)式側(cè)邊欄改進(jìn)版,結(jié)合導(dǎo)航條和下拉菜單進(jìn)行改進(jìn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-09-09
小程序?qū)崿F(xiàn)頁(yè)面多級(jí)來(lái)回切換的示例代碼
這篇文章主要為大家詳細(xì)介紹了小程序如何頁(yè)面多級(jí)來(lái)回切換支持滑動(dòng)和點(diǎn)擊操作,文中的實(shí)現(xiàn)步驟講解詳細(xì),快跟隨小編一起動(dòng)手嘗試一下吧2022-07-07

