《前端面試手記》之談談promise/async/await的執行順序

👇 內容速覽 👇javascript

  • 題目和答案
  • 輸出解釋
  • 再談談async/await
  • 最新的v8和谷歌瀏覽器的正確輸出

🔍查看所有教程 / 閱讀原文🔍css

1. 題目和答案

故事仍是要從下面這道面試題提及:請問下面這段代碼的輸出是什麼?
console.log('script start')

async function async1() {
  await async2()
  console.log('async1 end')
}

async function async2() {
  console.log('async2 end')
}
async1()

setTimeout(function() {
  console.log('setTimeout')
}, 0)

new Promise(resolve => {
  console.log('Promise')
  resolve()
})
  .then(function() {
    console.log('promise1')
  })
  .then(function() {
    console.log('promise2')
  })

console.log('script end')

上述,在Chrome 66node v10中,正確輸出是:前端

script start
async2 end
Promise
script end
promise1
promise2
async1 end
setTimeout
注意:在新版本的瀏覽器中, await輸出順序被「提早」了,請看官耐心慢慢看。

2. 流程解釋

邊看輸出結果,邊作解釋吧:java

  1. 正常輸出script start
  2. 執行async1函數,此函數中又調用了async2函數,輸出async2 end。回到async1函數,遇到了await,讓出線程
  3. 遇到setTimeout,扔到下一輪宏任務隊列
  4. 遇到Promise對象,當即執行其函數,輸出Promise。其後的resolve,被扔到了微任務隊列
  5. 正常輸出script end
  6. 此時,這次Event Loop宏任務都執行完了。來看下第二步被扔進來的微任務,由於async2函數是async關鍵詞修飾,所以,將await async2後的代碼扔到微任務隊列中
  7. 執行第4步被扔到微任務隊列的任務,輸出promise1promise2
  8. 執行第6步被扔到微任務隊列的任務,輸出async1 end
  9. 第一輪EventLoop完成,執行第二輪EventLoop。執行setTimeout中的回調函數,輸出setTimeout

3. 再談async和await

細心的朋友確定會發現前面第6步,若是async2函數是沒有async關鍵詞修飾的一個普通函數呢?node

// 新的async2函數
function async2() {
  console.log('async2 end')
}

輸出結果以下所示:webpack

script start
async2 end
Promise
script end
async1 end
promise1
promise2
setTimeout

不一樣的結果就出如今前面所說的第6步:若是await函數後面的函數是普通函數,那麼其後的微任務就正常執行;不然,會將其再放入微任務隊列。css3

4. 實際上是V8引擎的BUG

看到前面,正常人都會以爲真奇怪!(可是按照上面的訣竅倒也是能夠理解)git

然而V8團隊肯定了這是個bug(不少強行解釋要被打臉了),具體的PR請看這裏。好在,這個問題已經在最新的Chrome瀏覽器中被修復了es6

簡單點說,前面兩段不一樣代碼的運行結果都是:github

script start
async2 end
Promise
script end
async1 end
promise1
promise2
setTimeout

await就是讓出線程,其後的代碼放入微任務隊列(不會再多一次放入的過程),就這麼簡單了。

更多系列文章

⭐在GitHub上收藏/訂閱⭐

《前端知識體系》

《設計模式手冊》

《Webpack4漸進式教程》

⭐在GitHub上收藏/訂閱⭐

相關文章
相關標籤/搜索