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