注意:本次代碼僅在 Chrome 73 下進行測試。javascript
不瞭解 async await 的,先去看阮一峯老師的文章async 函數。java
先來看一道頭條的面試題,這實際上是考察瀏覽器的 event loop.git
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')
運行結果以下:es6
script start async1 start async2 promise1 script end async1 end promise2 setTimeout
首先 js 是單線程的,全部的任務都在主線程執行,而任務又分爲異步任務和同步任務。github
當主線程順序執行全部任務的時候,會執行遇到的同步任務,當遇到異步任務時,會把它推入一個額外的任務隊列中,直到全部的同步任務執行完畢,纔會取出任務隊列中的任務執行。面試
console.log('同步任務1') function test () { console.log('同步任務2') } test() setTimeout(() => console.log('異步任務3'), 0) console.log('同步任務4')
執行結果:segmentfault
同步任務1 同步任務2 同步任務4 // 異步任務最後才執行 異步任務3
然而異步任務還分爲微任務(micro-task)和宏任務(macro-task),不一樣的異步任務會進入不一樣的任務隊列中,而在同步任務執行完畢後,主線程會先取出全部的微任務執行,即清空微任務隊列,而後才取出一個宏任務執行,接着繼續清空微任務隊列,以後再取出一個宏任務執行,循環直至任務隊列爲空,這就是 event loop。promise
瀏覽器端的宏任務主要包含:script(總體代碼)、setTimeout、setInterval、I/O、UI交互事件;微任務主要包含:Promise、MutaionObserver(API 用來監視 DOM 變更)瀏覽器
console.log('同步任務1') function test () { console.log('同步任務2') } test() setTimeout(() => console.log('異步宏任務3'), 0) function asyncTask () { return new Promise((resolve, reject)=> { console.log('同步任務5') resolve() }) } asyncTask().then(() => console.log('異步微任務6')) console.log('同步任務4')
運行結果:異步
同步任務1 同步任務2 同步任務5 同步任務4 // 微任務是先於宏任務的 異步微任務6 異步宏任務3
注意:promise 裏面的代碼是當即執行的,then 函數纔是異步的。
首先 async 是基於 promise 的,async 函數執行完畢會返回一個 promise。
async function asyncFunc () { return 'hhh' } // Promise {<resolved>: "hhh"} console.log(asyncFunc())
注意:async 函數返回的 Promise 對象,必須等到內部全部await
執行完,纔會發生狀態改變,除非遇到return
語句或者拋出錯誤。也就是說,只有async
函數內部的異步操做執行完,纔會執行then
方法指定的回調函數。
在執行 async 函數時,碰到了 await 的話,會當即執行緊跟 await 的語句,而後把後續代碼推入微任務隊列(以這種形式去理解,實際執行並不是如此)。
來看這一個例子:
const syn1 = () => console.log(2) const syn2 = () => new Promise((r, j)=>r()).then(()=>console.log(3)) async function asyncFunc () { console.log('start') await console.log(1); await syn1() await syn2() console.log('end') return 7 } setTimeout(() => console.log(5), 0) console.log(0) asyncFunc().then(v => console.log(v)) new Promise((r, j)=>r()).then(() => console.log(6)) console.log(4)
運行結果:
0 start 1 4 2 6 3 end 7 5
執行順序是這樣的:
首先執行 setTimeout,把函數() => console.log(5)
推入異步宏任務隊列,進行計時 0 毫秒(即便是 0 毫秒也是存在至少 4 毫秒的延遲的。)
// 僞代碼 //此時異步宏任務隊列 [ 0: () => console.log(5) ]
執行 console.log(0),打印 0
執行 asyncFunc(),進入 asyncFunc 函數
執行 console.log('start'),打印 start
執行 await console.log(1),打印 1
把 asyncFunc 函數後續代碼推入微任務隊列
//此時微任務隊列 [ 0: ( await syn1() await syn2() console.log('end') return 7 ) ]
執行 new Promise((r, j)=>r()).then(() => console.log(6))
,把 then 中的 () => console.log(6)
推入微任務隊列
//此時微任務隊列 [ 0: ( await syn1() await syn2() console.log('end') return 7 ), 1: () => console.log(6) ]
執行 console.log(4),打印 4
同步任務執行完畢,開始清空微任務隊列
執行微任務 0: ( await syn1() await syn2() console.log('end') return 7 )
,遇到 await,執行 syn1()
進入 syn1函數,執行 console.log(2),打印 2,把後續代碼推入微任務隊列
//此時微任務隊列 [ 0: () => console.log(6), 1: ( await syn2() console.log('end') return 7 ) ]
微任務隊列非空,繼續執行微任務0: () => console.log(6)
,打印 6
//此時微任務隊列 [ 0: ( await syn2() console.log('end') return 7 ) ]
微任務隊列非空,繼續執行微任務0: ( await syn2() console.log('end') return 7 )
,遇到 await,執行 syn2()
進入 syn2 函數,執行new Promise((r, j)=>r()).then(()=>console.log(3))
,把 then 中的 () => console.log(3)
推入微任務隊列
//此時微任務隊列 [ 0: () => console.log(3) ]
把後續代碼推入微任務隊列
//此時微任務隊列 [ 0: () => console.log(3), 1: ( console.log('end') return 7 ) ]
微任務隊列非空,繼續執行微任務0: console.log('3')
,打印 3
//此時微任務隊列 [ 0: ( console.log('end') return 7 ) ]
微任務隊列非空,繼續執行微任務0: ( console.log('end') return 7 )
,打印 end,繼續執行 return 7
,asyncFunc 函數執行完畢,返回 Promise.resolve(7),執行then(v => console.log(v))
,把v => console.log(v)
推入微任務隊列
//此時微任務隊列 [ 0: console.log(7) ]
微任務隊列非空,繼續執行微任務0: console.log(7)
,打印 7
微任務隊列爲空,執行宏任務0: () => console.log(5)
,打印 5
微任務隊列爲空,宏任務隊列爲空,執行完畢。
其實 async await 的執行並不是如此,真正的執行方法是 await 會阻塞後面的代碼,讓出線程,先執行 async 函數外面的同步代碼,等同步代碼執行完,再回到 async 內部繼續執行。
讓咱們回到開頭的面試題:
// 原 async1,async2 async function async1() { console.log('async1 start') await async2() console.log('async1 end') } async function async2() { console.log('async2') } // 轉換成這樣 function async1() { console.log('async1 start') Promise.resolve(async2()).then(() => { console.log('async1 end') return Promise.resolve() }) } function async2() { console.log('async2') return Promise.resolve() }
但以這種形式去理解是否是比規範中那晦澀的英文更好理解呢。
最後附上TC39規範。