明明有了 promise ,為啥還需要 async await ?
↓推薦關(guān)注↓
為了讓還沒聽說過這個特性的小伙伴們有一個大致了解,以下是一些關(guān)于該特性的簡要介紹:
async/await是一種編寫異步代碼的新方法。在這之前編寫異步代碼使用的是回調(diào)函數(shù)和promise。
async/await實(shí)際是建立在promise之上的。因此你不能把它和回調(diào)函數(shù)搭配使用。
async/await可以使異步代碼在形式上更接近于同步代碼。這就是它最大的價值。
語法
假設(shè)有一個getJSON方法,它返回一個promise,該promise會被resolve為一個JSON對象。我們想要調(diào)用該方法,輸出得到的JSON對象,最后返回"done"。
以下是使用promise的實(shí)現(xiàn)方式:
const?makeRequest?=?()?=>
??getJSON()
????.then(data?=>?{
??????console.log(data)
??????return?"done"
????})
makeRequest()
使用async/await則是這樣的:
const?makeRequest?=?async?()?=>?{
??console.log(await?getJSON())
??return?"done"
}
makeRequest()
使用async/await時有以下幾個區(qū)別:
在定義函數(shù)時我們使用了async關(guān)鍵字。await關(guān)鍵字只能在使用async定義的函數(shù)的內(nèi)部使用。所有async函數(shù)都會返回一個promise,該promise最終resolve的值就是你在函數(shù)中return的內(nèi)容。
由于第一點(diǎn)中的原因,你不能在頂級作用域中await一個函數(shù)。因?yàn)轫敿壸饔糜虿皇且粋€async方法。
//?this?will?not?work?in?top?level
//?await?makeRequest()
????
//?this?will?work
makeRequest().then((result)?=>?{
??//?do?something
})
await getJSON()意味著直到getJSON()返回的promise在resolve之后,console.log才會執(zhí)行并輸出resolove的值。
為何使用async/await編寫出來的代碼更好呢?
1. 簡潔
看看我們節(jié)省了多少代碼吧。即使是在這么一個簡單的例子中,我們也節(jié)省了可觀的代碼。我們不需要為.then編寫一個匿名函數(shù)來處理返回結(jié)果,也不需要創(chuàng)建一個data變量來保存我們實(shí)際用不到的值。我們還避免了代碼嵌套。這些小優(yōu)點(diǎn)會在真實(shí)項(xiàng)目中變得更加明顯。
2. 錯誤處理
async/await終于使得用同一種構(gòu)造(古老而好用的try/catch) 處理同步和異步錯誤成為可能。在下面這段使用promise的代碼中,try/catch不能捕獲JSON.parse拋出的異常,因?yàn)樵摬僮魇窃趐romise中進(jìn)行的。要處理JSON.parse拋出的異常,你需要在promise上調(diào)用.catch并重復(fù)一遍異常處理的邏輯。通常在生產(chǎn)環(huán)境中異常處理邏輯都遠(yuǎn)比console.log要復(fù)雜,因此這會導(dǎo)致大量的冗余代碼。
const?makeRequest?=?()?=>?{
????try?{
????getJSON()
??????.then(result?=>?{
????????//?this?parse?may?fail
????????const?data?=?JSON.parse(result)
????????console.log(data)
??????})
??????//?uncomment?this?block?to?handle?asynchronous?errors
??????//?.catch((err)?=>?{
??????//???console.log(err)
??????//?})
????}?catch?(err)?{
????????console.log(err)
????}
}
現(xiàn)在看看使用了async/await的情況,catch代碼塊現(xiàn)在可以捕獲JSON.parse拋出的異常了:
const?makeRequest?=?async?()?=>?{
??try?{
????//?this?parse?may?fail
????const?data?=?JSON.parse(await?getJSON())
????console.log(data)
??}?catch?(err)?{
????console.log(err)
??}
}
3. 條件分支
假設(shè)有如下邏輯的代碼。請求數(shù)據(jù),然后根據(jù)返回?cái)?shù)據(jù)中的某些內(nèi)容決定是直接返回這些數(shù)據(jù)還是繼續(xù)請求更多數(shù)據(jù):
const?makeRequest?=?()?=>?{
??return?getJSON()
????.then(data?=>?{
??????if?(data.needsAnotherRequest)?{
????????return?makeAnotherRequest(data)
??????????.then(moreData?=>?{
????????????console.log(moreData)
????????????return?moreData
??????????})
??????}?else?{
????????console.log(data)
????????return?data
??????}
????})
}
只是閱讀這些代碼已經(jīng)夠讓你頭疼的了。一不小心你就會迷失在這些嵌套(6層),空格,返回語句中。(當(dāng)然我們一般用請求數(shù)據(jù)的返回值作為判斷條件不會寫成這樣,也許我這個小白會...) 在使用async/await改寫后,這段代碼的可讀性大大提高了:
const?makeRequest?=?async?()?=>?{
??const?data?=?await?getJSON()
??if?(data.needsAnotherRequest)?{
????const?moreData?=?await?makeAnotherRequest(data);
????console.log(moreData)
????return?moreData
??}?else?{
????console.log(data)
????return?data????
??}
}
4. 中間值
比如你向一個url1發(fā)送請求,拿到返回值1,然后用這個返回值1當(dāng)作參數(shù)去請求url2,拿到返回值2,然后拿返回值1和返回值2作為參數(shù)去請求url3,拿到最終的返回結(jié)果。
對應(yīng)的代碼看起來是這樣的:
const?makeRequest?=?()?=>?{
??return?promise1()
????.then(value1?=>?{
??????//?do?something
??????return?promise2(value1)
????????.then(value2?=>?{
??????????//?do?something??????????
??????????return?promise3(value1,?value2)
????????})
????})
}
如果promise3沒有用到value1,那么我們就可以把這幾個promise改成嵌套的模式。如果你不喜歡這種編碼方式,你也可以把value1和value2封裝在一個Promsie.all調(diào)用中以避免深層次的嵌套:
const?makeRequest?=?()?=>?{
??return?promise1()
????.then(value1?=>?{
??????//?do?something
??????return?Promise.all([value1,?promise2(value1)])
????})
????.then(([value1,?value2])?=>?{
??????//?do?something??????????
??????return?promise3(value1,?value2)
????})
}
這種方式為了保證可讀性而犧牲了語義。除了避免嵌套的promise,沒有其它理由要把value1和value2放到一個數(shù)組里。
同樣的邏輯如果換用async/await編寫就會非常簡單,直觀。
const?makeRequest?=?async?()?=>?{
??const?value1?=?await?promise1()
??const?value2?=?await?promise2(value1)
??return?promise3(value1,?value2)
}
5. 異常堆棧
假設(shè)有一段串行調(diào)用多個promise的代碼,在promise串中的某一點(diǎn)拋出了異常:
const?makeRequest?=?()?=>?{
??return?callAPromise()
????.then(()?=>?callAPromise())
????.then(()?=>?callAPromise())
????.then(()?=>?callAPromise())
????.then(()?=>?callAPromise())
????.then(()?=>?{
??????throw?new?Error("oops");
????})
}
makeRequest()
??.catch(err?=>?{
????console.log(err);
????//?output
????//?Error:?oops?at?callAPromise.then.then.then.then.then?(index.js:8:13)
??})
從promise串返回的異常堆棧中沒有包含關(guān)于異常是從哪一個環(huán)節(jié)拋出的信息。更糟糕的是,它還會誤導(dǎo)你,它包含的唯一的函數(shù)名是callAPromise,然而該函數(shù)與此異常并無關(guān)系。(這種情況下文件名和行號還是有參考價值的)。
然而,在使用了async/await的代碼中,異常堆棧指向了正確的函數(shù):
const?makeRequest?=?async?()?=>?{
??await?callAPromise()
??await?callAPromise()
??await?callAPromise()
??await?callAPromise()
??await?callAPromise()
??throw?new?Error("oops");
}
makeRequest()
??.catch(err?=>?{
????console.log(err);
????//?output
????//?Error:?oops?at?makeRequest?(index.js:7:9)
??})
這帶來的好處在本地開發(fā)環(huán)境中可能并不明顯,但當(dāng)你想要在生產(chǎn)環(huán)境的服務(wù)器上獲取有意義的異常信息時,這會非常有用。在這種情況下,知道異常來自makeRequest而不是一連串的then調(diào)用會有意義的多。
6. 調(diào)試
最后壓軸的一點(diǎn),使用async/await最大的優(yōu)勢在于它很容易被調(diào)試。由于以下兩個原因,調(diào)試promise一直以來都是很痛苦的。
你不能在一個返回表達(dá)式的箭頭函數(shù)中設(shè)置斷點(diǎn)(因?yàn)闆]有代碼塊)

如果你在一個.then代碼塊中使用調(diào)試器的步進(jìn)(step-over)功能,調(diào)試器并不會進(jìn)入后續(xù)的.then代碼塊,因?yàn)檎{(diào)試器只能跟蹤同步代碼的『每一步』。
通過使用async/await,你不必再使用箭頭函數(shù)。你可以對await語句執(zhí)行步進(jìn)操作,就好像他們都是普通的同步調(diào)用一樣。

結(jié)論 async/await是過去幾年中JavaScript引入的最具革命性的特性之一。它使你意識到promise在語法上的糟糕之處,并提供了一種簡單,直接的替代方案。
參考文章
https://loveky.github.io/2017/04/09/translte-6-reasons-why-javascripts-async-await-blows-promises-away/
轉(zhuǎn)自:Angus安格斯
https://juejin.cn/post/6960855679208783903
最后
歡迎關(guān)注【前端瓶子君】??ヽ(°▽°)ノ?回復(fù)「算法」,加入前端編程源碼算法群,每日一道面試題(工作日),第二天瓶子君都會很認(rèn)真的解答喲!
回復(fù)「交流」,吹吹水、聊聊技術(shù)、吐吐槽!回復(fù)「閱讀」,每日刷刷高質(zhì)量好文!如果這篇文章對你有幫助,「在看」是最大的支持
?》》面試官也在看的算法資料《《
“在看和轉(zhuǎn)發(fā)”就是最大的支持
