詳解JavaScript異步編程之a(chǎn)sync和await
當(dāng)前位置:點(diǎn)晴教程→知識管理交流
→『 技術(shù)文檔交流 』
經(jīng)過了Generator的過渡之后異步代碼同步化的需求逐漸成為了主流需求,雖然Generator函數(shù)能夠?qū)崿F(xiàn)異步編程,但實(shí)際上我們很少用它來實(shí)現(xiàn)異步,因?yàn)?/span>在ES7版本中得到了提案,并在ES8版本中進(jìn)⾏了實(shí)現(xiàn)的 async 函數(shù)對Generator函數(shù)的流程又做了一層封裝,定義了全新的異步控制流程,使得異步方案使用更加方便。
async/await的代碼結(jié)構(gòu)的編寫⽅式與Generator函數(shù)結(jié)構(gòu)很相似,async就相當(dāng)于那個(gè)(*),await就相當(dāng)于yield。提案中規(guī)定了可以使⽤async修飾⼀個(gè)函數(shù),這樣就能在該函數(shù)的直接⼦作⽤域中,使⽤await來⾃動(dòng)的控制函數(shù)的流程,await 右側(cè)可以編寫任何變量或?qū)ο螅?dāng)右側(cè)是普通對象的時(shí)候函數(shù)會(huì)⾃動(dòng)返回右側(cè)的結(jié)果并向下執(zhí)⾏,⽽當(dāng)await右側(cè)為Promise對象時(shí),如果Promise對象狀態(tài)沒有變成完成,函數(shù)就會(huì)掛起等待,直到Promise對象變成fulfilled,程序再向下執(zhí)⾏,并且Promise的值會(huì)⾃動(dòng)返回給await左側(cè)的變量中。async和await需要成對出現(xiàn),async可以單獨(dú)修飾函數(shù),但是await只能在被async修飾的函數(shù)中使⽤。 有了await和async就相當(dāng)于使⽤了⾃帶執(zhí)⾏函數(shù)的Generator函數(shù),這樣我們就不再需要單獨(dú)針對Generator函數(shù)進(jìn)⾏開發(fā)了,所以async和await逐漸成為主流異步流程控制的終極解決⽅案。⽽Generator慢慢淡出了業(yè)務(wù)開發(fā)者的舞臺(tái),不過Generator函數(shù)成為了向下兼容過渡期版本瀏覽器的候補(bǔ)實(shí)現(xiàn)⽅式,雖然在現(xiàn)今的⼤部分項(xiàng)⽬業(yè)務(wù)中使⽤Generator函數(shù)的場景⾮常的少,但是如果查看腳⼿架項(xiàng)⽬中通過babel構(gòu)建的JavaScript⽣產(chǎn)代碼,我們還是能⼤量的發(fā)現(xiàn)Generator的應(yīng)⽤的,它的作⽤就是為了兼容不⽀持async和await的瀏覽器。 async的英文意思是異步,當(dāng)函數(shù)前面有async關(guān)鍵字并且該函數(shù)有返回值時(shí),函數(shù)執(zhí)行成功,函數(shù)就會(huì)調(diào)用Promise.resove()并隱式的返回一個(gè)Promise對象;如果函數(shù)執(zhí)行失敗就會(huì)調(diào)用Promise.reject()并返回一個(gè)Promise對象。
根據(jù)控制臺(tái)結(jié)果我們發(fā)現(xiàn)其實(shí)async修飾的函數(shù),本身就是⼀個(gè)Promise對象,雖然我們在函數(shù)中return的值是1,是使⽤了async修飾之后,這個(gè)函數(shù)運(yùn)⾏時(shí)并沒有直接返回1,⽽是返回了⼀個(gè)值為1的Promise對象。 async函數(shù)中如果有異步操作會(huì)進(jìn)行等待,但是async函數(shù)本身會(huì)馬上返回,不會(huì)阻塞當(dāng)前線程。async函數(shù)被調(diào)用不會(huì)阻塞界面渲染,內(nèi)部由await關(guān)鍵字修飾異步過程,會(huì)阻塞等待異步任務(wù)的完成再返回。 如果在函數(shù)中return一個(gè)直接量,async會(huì)把這個(gè)直接量通過Promise.resolve(直接量) 封裝成 Promise 對象 ,如果沒有返回值,相當(dāng)于返回了Promise.resolve(undefined)。 我們可以通過Promise.then()回調(diào)得到async函數(shù)的返回值,因?yàn)樵摵瘮?shù)返回的是Promise對象。
只有async函數(shù)內(nèi)部的異步操作執(zhí)行完,才會(huì)執(zhí)行then方法指定的回調(diào)函數(shù)。 看下面代碼:
通過控制臺(tái)看到打印順序?yàn)?、3、2。按照Promise對象的執(zhí)⾏流程function被async修飾之后它本身應(yīng)該變成異步函數(shù),那么它應(yīng)該在1和2輸出完畢之后再輸出3,但是結(jié)果卻出⼈意料,這是為什么呢? 我們回想一下Promise函數(shù)的結(jié)構(gòu)。
在介紹Promise對象時(shí),我們知道new Promise時(shí)的function是同步流程,而then()是異步的,這也就不難解釋為什么輸出結(jié)果是1、3、2了。 await的英文意思是等待,等待的是一個(gè)表達(dá)式,這個(gè)表達(dá)式的計(jì)算結(jié)果是 Promise 對象或者其它值,得到resolve的值作為await表達(dá)式的運(yùn)算結(jié)果。 因?yàn)?async 函數(shù)返回一個(gè) Promise 對象,所以 await 可以用于等待一個(gè) async 函數(shù)的返回值,這也可以說是 await 在等 async 函數(shù),實(shí)際上它等待的是一個(gè)返回值。await 不僅僅用于等 Promise 對象,它可以等任意表達(dá)式的結(jié)果。 await 表達(dá)式的運(yùn)算結(jié)果取決于它等的東西。如果它等到的不是一個(gè) Promise 對象,相當(dāng)于 await Promise.resolve(...),那 await 表達(dá)式的運(yùn)算結(jié)果就是它等到的東西。 如果它等到的是一個(gè) Promise 對象,它會(huì)阻塞后面的代碼,等著 Promise 對象 resolve,然后得到 resolve 的值,作為 await 表達(dá)式的運(yùn)算結(jié)果。 依然以上面的代碼為例,稍加改造。
通過控制臺(tái)我們可以看到打印順序?yàn)?->3->2->4。可以看到await 4表達(dá)式會(huì)將4作為其運(yùn)算結(jié)果賦值給a,并且await會(huì)阻塞后面的console.log(a)的執(zhí)行,所以最后才會(huì)打印出4,這就是 await 必須用在 async 函數(shù)中的原因。 我們將上面的函數(shù)翻譯⼀下,由于async修飾的函數(shù)會(huì)被解釋成Promise對象,所以我們可以將其翻譯成如下結(jié)構(gòu):
看到這個(gè)Promise對象我們就明白了,由于初始化的回調(diào)是同步的所以1,3,2都是同步代碼,⽽4是在resolve中傳⼊的,then代表異步回調(diào)所以4應(yīng)該最后輸出。 綜上所述,async函數(shù)中有⼀個(gè)最大的特點(diǎn),就是第⼀個(gè)await會(huì)作為分⽔嶺⼀般的存在,在第⼀個(gè)await的右側(cè)和上⾯的代碼,全部是同步代碼區(qū)域相當(dāng)于new Promise的回調(diào),第⼀個(gè)await的左側(cè)和下⾯的代碼,就變成了異步代碼區(qū)域相當(dāng)于then的回調(diào)。 假設(shè)一個(gè)業(yè)務(wù),分多個(gè)步驟完成,每個(gè)步驟都是異步的,而且依賴于上一個(gè)步驟的結(jié)果。在過去的編程中JavaScript的主要異步處理⽅式,是采⽤回調(diào)函數(shù)的⽅式來進(jìn)⾏處理,想要保證n個(gè)步驟的異步編程有序進(jìn)⾏,會(huì)出現(xiàn)如下的代碼。
雖然可以Promise 通過 then 鏈來解決多層回調(diào)的問題,但是現(xiàn)在有了async/await我們可使用它來進(jìn)一步優(yōu)化上面的代碼。
用 Promise 方式來實(shí)現(xiàn)這三個(gè)步驟。
如果用 async/await 來實(shí)現(xiàn)如下。
現(xiàn)在我們可以使⽤如上的⽅式來進(jìn)⾏流程控制,不再需要依賴自己定義的流程控制器函數(shù)來進(jìn)⾏分步執(zhí)⾏,這⼀切的核⼼起源都是Promise對象的規(guī)則定義開始的。使用async/await結(jié)果和之前的 Promise 實(shí)現(xiàn)是一樣的,但是這個(gè)代碼看起來是不是清晰得多,幾乎跟同步代碼一樣。 1、await只能用在async函數(shù)之中,也就是說await必須和async一起使用,反之,async可以單獨(dú)只用。2、await后面跟著是一個(gè)Promise對象,會(huì)等待Promise返回結(jié)果了,再繼續(xù)執(zhí)行后面的代碼。
3、await后面跟著的是一個(gè)數(shù)值或者是字符串等數(shù)據(jù)類型的值,則直接返回該值。 4、await后面跟著的是定時(shí)器,不會(huì)等待定時(shí)器里面的代碼執(zhí)行完,而是直接執(zhí)行后面的代碼,然后再執(zhí)行定時(shí)器中的代碼。
5、可以直接用標(biāo)準(zhǔn)的try...catch...語法捕捉錯(cuò)誤。
從回調(diào)地獄到Promise的鏈?zhǔn)秸{(diào)⽤到Generator函數(shù)的分步執(zhí)⾏再到async和await的⾃動(dòng)異步代碼同步化機(jī)制,經(jīng)歷了很多個(gè)年頭,所以⾯試中為什么經(jīng)常問到Promise,并且重點(diǎn)沿著Promise對象深⼊的挖掘去問你各種問題,主要是考察程序員對Promise對象本身以及他的發(fā)展歷程是否有深⼊的了解,同時(shí)也是在考察⾯試者對JavaScript的事件循環(huán)系統(tǒng)和異步編程的基本功是否⾜夠的扎實(shí)。Promise和事件循環(huán)系統(tǒng)并不是JavaScript中的⾼級知識,⽽是真正的基礎(chǔ)知識,所以所有⼈想要在⾏業(yè)中更好的發(fā)展下去,這些知識都是必備基礎(chǔ),必須扎實(shí)掌握。 該文章在 2024/4/1 15:05:16 編輯過 |
關(guān)鍵字查詢
相關(guān)文章
正在查詢... |