摘要:第部分畫圖一步步看清宏任務(wù)微任務(wù)的執(zhí)行過程我們以開篇的經(jīng)典面試題為例,分析這個(gè)例子中的宏任務(wù)和微任務(wù)。注意這里只是把推入微任務(wù)隊(duì)列,并沒有執(zhí)行。執(zhí)行結(jié)束,才能繼續(xù)執(zhí)行后面的代碼如圖此時(shí)當(dāng)前宏任務(wù)都執(zhí)行完了,要處理微任務(wù)隊(duì)列里的代碼。
8張圖讓你一步步看清 async/await 和 promise 的執(zhí)行順序
為什么寫這篇文章?
測試一下自己有沒有必要看
需要具備的前置基礎(chǔ)知識(shí)
主要內(nèi)容
對于async await的理解
畫圖一步步看清宏任務(wù)、微任務(wù)的執(zhí)行過程
為什么寫這篇文章?說實(shí)話,關(guān)于js的異步執(zhí)行順序,宏任務(wù)、微任務(wù)這些,或者async/await這些慨念已經(jīng)有非常多的文章寫了。
但是怎么說呢,簡單來說,業(yè)務(wù)中很少用async,不太懂a(chǎn)sync呢,
研究了一天,感覺懂了,所手癢想寫一篇 ,哈哈
畢竟自己學(xué)會(huì)的知識(shí),如果連表達(dá)清楚都做不到,怎么能指望自己用好它呢?
測試一下自己有沒有必要看所以我寫這個(gè)的文章,主要還是交流學(xué)習(xí),如果您已經(jīng)清楚了eventloop/async/await/promise這些東西呢,可以 break 啦
有說的不對的地方,歡迎留言討論,
那么還是先通過一道題自我檢測一下,是否有必要繼續(xù)看下去把。
其實(shí)呢,這是去年一道爛大街的「今日頭條」的面試題 。
我覺得這道題的關(guān)鍵,不僅是說出正確的打印順序,更重要的能否說清楚每一個(gè)步驟,為什么這樣執(zhí)行。
async function async1() { console.log( "async1 start" ) await async2() console.log( "async1 end" ) } async function async2() { console.log( "async2" ) } console.log( "script start" ) setTimeout( function () { console.log( "setTimeout" ) }, 0 ) async1(); new Promise( function ( resolve ) { console.log( "promise1" ) resolve(); } ).then( function () { console.log( "promise2" ) } ) console.log( "script end" )
注:因?yàn)槭且坏狼岸嗣嬖囶},所以答案是以瀏覽器的eventloop機(jī)制為準(zhǔn)的,在node平臺(tái)上運(yùn)行會(huì)有差異。
script start async1 start async2 promise1 script end promise2 async1 end setTimeout
如果你發(fā)現(xiàn)運(yùn)行結(jié)果跟自己想的一樣,可以選擇跳過這篇文章啦,
或者如果你有興趣看看俺倆的理解有沒有區(qū)別,可以跳到后面的 「畫圖講解的部分」
需要具備的前置知識(shí)promise的使用經(jīng)驗(yàn)
瀏覽器端的eventloop
不過如果是對 ES7 的 async 不太熟悉,是沒關(guān)系的哈,因?yàn)檫@篇文章會(huì)詳解 async。
那么如果不具備這些知識(shí)呢,推薦幾篇我覺得講得比較清楚的文章
https://segmentfault.com/a/11... 這是我之前寫的講解eventloop的文章,我覺得還算清晰,但是沒涉及 async
https://segmentfault.com/a/11... 這是我讀過的講async await最清楚的文章
http://es6.ruanyifeng.com/#do... promise就推薦阮一峰老師的ES6吧,不過不熟悉 promise 的應(yīng)該較少啦。
主要內(nèi)容 第1部分:對于async await的理解我推薦的那篇文章,對 async/await 講得更詳細(xì)。不過我希望自己能更加精煉的幫你理解它們
這部分,主要會(huì)講解 3 點(diǎn)內(nèi)容
1.async 做一件什么事情?
2.await 在等什么?
3.await 等到之后,做了一件什么事情?
4.補(bǔ)充: async/await 比 promise有哪些優(yōu)勢?(回頭補(bǔ)充)
1.async 做一件什么事情?
一句話概括: 帶 async 關(guān)鍵字的函數(shù),它使得你的函數(shù)的返回值必定是 promise 對象
也就是
如果async關(guān)鍵字函數(shù)返回的不是promise,會(huì)自動(dòng)用Promise.resolve()包裝
如果async關(guān)鍵字函數(shù)顯式地返回promise,那就以你返回的promise為準(zhǔn)
這是一個(gè)簡單的例子,可以看到 async 關(guān)鍵字函數(shù)和普通函數(shù)的返回值的區(qū)別
async function fn1(){ return 123 } function fn2(){ return 123 } console.log(fn1()) console.log(fn2())
Promise?{: 123} 123
所以你看,async 函數(shù)也沒啥了不起的,以后看到帶有 async 關(guān)鍵字的函數(shù)也不用慌張,你就想它無非就是把return值包裝了一下,其他就跟普通函數(shù)一樣。
關(guān)于async關(guān)鍵字還有那些要注意的?
在語義上要理解,async表示函數(shù)內(nèi)部有異步操作
另外注意,一般 await 關(guān)鍵字要在 async 關(guān)鍵字函數(shù)的內(nèi)部,await 寫在外面會(huì)報(bào)錯(cuò)。
2.await 在等什么?
一句話概括: await等的是右側(cè)「表達(dá)式」的結(jié)果
也就是說,
右側(cè)如果是函數(shù),那么函數(shù)的return值就是「表達(dá)式的結(jié)果」
右側(cè)如果是一個(gè) "hello" 或者什么值,那表達(dá)式的結(jié)果就是 "hello"
async function async1() { console.log( "async1 start" ) await async2() console.log( "async1 end" ) } async function async2() { console.log( "async2" ) } async1() console.log( "script start" )
這里注意一點(diǎn),可能大家都知道await會(huì)讓出線程,阻塞后面的代碼,那么上面例子中, "async2" 和 "script start" 誰先打印呢?
是從左向右執(zhí)行,一旦碰到await直接跳出, 阻塞async2()的執(zhí)行?
還是從右向左,先執(zhí)行async2后,發(fā)現(xiàn)有await關(guān)鍵字,于是讓出線程,阻塞代碼呢?
實(shí)踐的結(jié)論是,從右向左的。先打印async2,后打印的script start
之所以提一嘴,是因?yàn)槲医?jīng)常看到這樣的說法,「一旦遇到await就立刻讓出線程,阻塞后面的代碼」
這樣的說法,會(huì)讓我誤以為,await后面那個(gè)函數(shù), async2()也直接被阻塞呢。
3.await 等到之后,做了一件什么事情?
那么右側(cè)表達(dá)式的結(jié)果,就是await要等的東西。
等到之后,對于await來說,分2個(gè)情況
不是promise對象
是promise對象
如果不是 promise , await會(huì)阻塞后面的代碼,先執(zhí)行async外面的同步代碼,同步代碼執(zhí)行完,再回到async內(nèi)部,把這個(gè)非promise的東西,作為 await表達(dá)式的結(jié)果
如果它等到的是一個(gè) promise 對象,await 也會(huì)暫停async后面的代碼,先執(zhí)行async外面的同步代碼,等著 Promise 對象 fulfilled,然后把 resolve 的參數(shù)作為 await 表達(dá)式的運(yùn)算結(jié)果。
第2部分:畫圖一步步看清宏任務(wù)、微任務(wù)的執(zhí)行過程我們以開篇的經(jīng)典面試題為例,分析這個(gè)例子中的宏任務(wù)和微任務(wù)。
async function async1() { console.log( "async1 start" ) await async2() console.log( "async1 end" ) } async function async2() { console.log( "async2" ) } console.log( "script start" ) setTimeout( function () { console.log( "setTimeout" ) }, 0 ) async1(); new Promise( function ( resolve ) { console.log( "promise1" ) resolve(); } ).then( function () { console.log( "promise2" ) } ) console.log( "script end" )
先分享一個(gè)我個(gè)人理解的宏任務(wù)和微任務(wù)的慨念,在我腦海中宏任務(wù)和為微任務(wù)如圖所示
也就是「宏任務(wù)」、「微任務(wù)」都是隊(duì)列。
一段代碼執(zhí)行時(shí),會(huì)先執(zhí)行宏任務(wù)中的同步代碼,
如果執(zhí)行中遇到setTimeout之類宏任務(wù),那么就把這個(gè)setTimeout內(nèi)部的函數(shù)推入「宏任務(wù)的隊(duì)列」中,下一輪宏任務(wù)執(zhí)行時(shí)調(diào)用。
如果執(zhí)行中遇到promise.then()之類的微任務(wù),就會(huì)推入到「當(dāng)前宏任務(wù)的微任務(wù)隊(duì)列」中,在本輪宏任務(wù)的同步代碼執(zhí)行都完成后,依次執(zhí)行所有的微任務(wù)1、2、3
下面就以面試題為例子,分析這段代碼的執(zhí)行順序.
每次宏任務(wù)和微任務(wù)發(fā)生變化,我都會(huì)畫一個(gè)圖來表示他們的變化。
直接打印同步代碼 console.log("script start")
首先是2個(gè)函數(shù)聲明,雖然有async關(guān)鍵字,但不是調(diào)用我們就不看。然后首先是打印同步代碼 console.log("script start")
將setTimeout放入宏任務(wù)隊(duì)列
默認(rèn)所包裹的代碼,其實(shí)可以理解為是第一個(gè)宏任務(wù),所以這里是宏任務(wù)2
調(diào)用async1,打印 同步代碼 console.log( "async1 start" )
我們說過看到帶有async關(guān)鍵字的函數(shù),不用害怕,它的僅僅是把return值包裝成了promise,其他并沒有什么不同的地方。所以就很普通的打印 console.log( "async1 start" )
分析一下 await async2()
前文提過await,1.它先計(jì)算出右側(cè)的結(jié)果,2.然后看到await后,中斷async函數(shù) - 先得到await右側(cè)表達(dá)式的結(jié)果。執(zhí)行async2(),打印同步代碼console.log("async2"), 并且return Promise.resolve(undefined) - await后,中斷async函數(shù),先執(zhí)行async外的同步代碼 目前就直接打印 console.log("async2")
被阻塞后,要執(zhí)行async之外的代碼
執(zhí)行new Promise(),Promise構(gòu)造函數(shù)是直接調(diào)用的同步代碼,所以 console.log( "promise1" )
代碼運(yùn)行到promise.then()
代碼運(yùn)行到promise.then(),發(fā)現(xiàn)這個(gè)是微任務(wù),所以暫時(shí)不打印,只是推入當(dāng)前宏任務(wù)的微任務(wù)隊(duì)列中。
注意:這里只是把promise2推入微任務(wù)隊(duì)列,并沒有執(zhí)行。微任務(wù)會(huì)在當(dāng)前宏任務(wù)的同步代碼執(zhí)行完畢,才會(huì)依次執(zhí)行
打印同步代碼 console.log( "script end" )
沒什么好說的。執(zhí)行完這個(gè)同步代碼后,「async外的代碼」終于走了一遍 下面該回到 await 表達(dá)式那里,執(zhí)行await Promise.resolve(undefined)了
回到async內(nèi)部,執(zhí)行await Promise.resolve(undefined)
這部分可能不太好理解,我盡量表達(dá)我的想法。
對于 await Promise.resolve(undefined) 如何理解呢?
https://developer.mozilla.org...
根據(jù) MDN 原話我們知道
如果一個(gè) Promise 被傳遞給一個(gè) await 操作符,await 將等待 Promise 正常處理完成并返回其處理結(jié)果。
在我們這個(gè)例子中,就是Promise.resolve(undefined)正常處理完成,并返回其處理結(jié)果。那么await async2()就算是執(zhí)行結(jié)束了。
目前這個(gè)promise的狀態(tài)是fulfilled,等其處理結(jié)果返回就可以執(zhí)行await下面的代碼了。
那何時(shí)能拿到處理結(jié)果呢?
回憶平時(shí)我們用promise,調(diào)用resolve后,何時(shí)能拿到處理結(jié)果?是不是需要在then的第一個(gè)參數(shù)里,才能拿到結(jié)果。
(調(diào)用resolve時(shí),會(huì)把then的參數(shù)推入微任務(wù)隊(duì)列,等主線程空閑時(shí),再調(diào)用它)
所以這里的 await Promise.resolve() 就類似于
Promise.resolve(undefined).then((undefined) => { })
把then的第一個(gè)回調(diào)參數(shù) (undefined) => {} 推入微任務(wù)隊(duì)列。
then執(zhí)行完,才是await async2()執(zhí)行結(jié)束。
await async2()執(zhí)行結(jié)束,才能繼續(xù)執(zhí)行后面的代碼
如圖
此時(shí)當(dāng)前宏任務(wù)1都執(zhí)行完了,要處理微任務(wù)隊(duì)列里的代碼。
微任務(wù)隊(duì)列,先進(jìn)先出的原則,
執(zhí)行微任務(wù)1,打印promise2
執(zhí)行微任務(wù)2,沒什么內(nèi)容..
但是微任務(wù)2執(zhí)行后,await async2()語句結(jié)束,后面的代碼不再被阻塞,所以打印
console.log( "async1 end" )
宏任務(wù)1執(zhí)行完成后,執(zhí)行宏任務(wù)2
宏任務(wù)2的執(zhí)行比較簡單,就是打印
console.log("setTimeout")
補(bǔ)充在不同瀏覽器上的測試結(jié)果谷歌瀏覽器,目前是版本是「版本 71.0.3578.80(正式版本) (64 位)」 Mac操作系統(tǒng)
Safari瀏覽器的測試結(jié)果
火狐瀏覽器的測試結(jié)果
如果不理解可以留言,有錯(cuò)誤的話也歡迎指正。
關(guān)于執(zhí)行順序評論區(qū)有指出
Chrome72 dev版本的執(zhí)行順序是Promise2后打印,
或者是babel編譯過后的代碼是promise2后打印。
我自己也實(shí)踐了一下babel編譯后的代碼執(zhí)行順序的確是promise2后打印的..
原因是ESMA最新規(guī)范的有修改,然后這一點(diǎn)的詳情,說實(shí)話我目前也不是很清楚,評論區(qū)有給出資料,可供參考討論。
https://github.com/rhinel/blo...
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/99663.html
摘要:一篇文章和一道面試題最近,有篇名為張圖幫你一步步看清和的執(zhí)行順序的文章引起了我的關(guān)注。作者用一道年今日頭條的前端面試題為引子,分步講解了最終結(jié)果的執(zhí)行原因。從字面意思理解,讓我們等等。當(dāng)前的最新版本,在這里的執(zhí)行順序上,的確存在有問題。 一篇文章和一道面試題 最近,有篇名為 《8張圖幫你一步步看清 async/await 和 promise 的執(zhí)行順序》 的文章引起了我的關(guān)注。 作者用...
摘要:問題的關(guān)鍵在于其執(zhí)行過程中的微任務(wù)數(shù)量,下文中我們需要用上述代碼中的方式對微任務(wù)的執(zhí)行順序進(jìn)行標(biāo)記,以輔助我們理解這其中的執(zhí)行過程。 原文發(fā)布在掘金社區(qū):https://juejin.im/post/5c3cc981f265da616a47e028 起源 2019年了,相信大家對 Promise 和 async/await 都不再陌生了。 前幾日,我在社區(qū)讀到了一篇關(guān)于 async/...
摘要:網(wǎng)上找到的各種面試題整理,長期更新。大部分答案整理來自網(wǎng)絡(luò),有問題的地方,希望大家能指出,及時(shí)修改技術(shù)更新迭代,也會(huì)及時(shí)更新博客原地址前端前端性能優(yōu)化清理文檔,即超文本標(biāo)記語言,幾乎是所有網(wǎng)站的支柱。在最近更新的中,甚至可以創(chuàng)建圖表。 網(wǎng)上找到的各種面試題整理,長期更新。大部分答案整理來自網(wǎng)絡(luò),有問題的地方,希望大家能指出,及時(shí)修改;技術(shù)更新迭代,也會(huì)及時(shí)更新 博客原地址:https:...
摘要:缺點(diǎn)無法取消當(dāng)處于狀態(tài)時(shí),無法得知目前進(jìn)展到哪一個(gè)階段錯(cuò)誤不能被生成器什么是函數(shù)是提供的一種異步編程解決方案,語法行為與傳統(tǒng)函數(shù)完全不同函數(shù)有多種理解角度。 JavaScript的執(zhí)行機(jī)制在上篇文章中進(jìn)行了深入的探討,那么既然是一門單線程語言,如何進(jìn)行良好體驗(yàn)的異步編程呢 回調(diào)函數(shù)Callbacks 當(dāng)程序跑起來時(shí),一般情況下,應(yīng)用程序(application program)會(huì)時(shí)常通...
摘要:缺點(diǎn)無法取消當(dāng)處于狀態(tài)時(shí),無法得知目前進(jìn)展到哪一個(gè)階段錯(cuò)誤不能被生成器什么是函數(shù)是提供的一種異步編程解決方案,語法行為與傳統(tǒng)函數(shù)完全不同函數(shù)有多種理解角度。 JavaScript的執(zhí)行機(jī)制在上篇文章中進(jìn)行了深入的探討,那么既然是一門單線程語言,如何進(jìn)行良好體驗(yàn)的異步編程呢 回調(diào)函數(shù)Callbacks 當(dāng)程序跑起來時(shí),一般情況下,應(yīng)用程序(application program)會(huì)時(shí)常通...
閱讀 1922·2023-04-26 02:14
閱讀 3831·2021-11-23 09:51
閱讀 1455·2021-10-13 09:39
閱讀 4025·2021-09-24 10:36
閱讀 3062·2021-09-22 15:55
閱讀 3575·2019-08-30 12:57
閱讀 2080·2019-08-29 15:30
閱讀 2030·2019-08-29 13:19