「今日頭條」前端面試題和思路解析

一篇文章和一道面試題

最近,有篇名爲 《8張圖幫你一步步看清 async/await 和 promise 的執行順序》 的文章引發了個人關注。前端

做者用一道2017年「今日頭條」的前端面試題爲引子,分步講解了最終結果的執行緣由。其中涉及到了很多概念,好比異步的執行順序,宏任務,微任務等等,同時做者限定了執行範圍,以瀏覽器的 event loop 機制爲準。下面是原題的代碼:react

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');

緊接着,做者先給出了答案。並但願讀者先行自我測試。面試

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

我在看這道題的時候,先按照本身的理解寫出告終果。chrome

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

一些重要的概念

這裏須要先簡單地說一些 event loop 的概念。segmentfault

  • Javascript是單線程的,全部的同步任務都會在主線程中執行。
  • 主線程以外,還有一個任務隊列。每當一個異步任務有結果了,就往任務隊列裏塞一個事件。
  • 當主線程中的任務,都執行完以後,系統會 「依次」 讀取任務隊列裏的事件。與之相對應的異步任務進入主線程,開始執行。
  • 異步任務之間,會存在差別,因此它們執行的優先級也會有區別。大體分爲 微任務(micro task,如:Promise、MutaionObserver等)和宏任務(macro task,如:setTimeout、setInterval、I/O等)。同一次事件循環中,微任務永遠在宏任務以前執行。
  • 主線程會不斷重複上面的步驟,直到執行完全部任務。

另外,還有 async/await 的概念。promise

  • async 函數,能夠理解爲是Generator 函數的語法糖。
  • 它創建在promise之上,老是與await一塊兒使用的。
  • await會返回一個Promise 對象,或者一個表達式的值。
  • 其目的是爲了讓異步操做更優雅,能像同步同樣地書寫。

個人理解

再說說我對這道題的理解。瀏覽器

  • 首先,從console的數量上看,會輸出8行結果。
  • 再瞟了一眼代碼,看到了setTimeout,因而,默默地把它填入第8行。
  • 在setTimeout附近,看到了 console.log( 'script start' ) 和 async1(),能夠確認它們是同步任務,會先在主線程中執行。因此,妥妥地在第1行填入 script start,第2行填入async1方法中的第一行 async1 start。
  • 接下來,遇到了await。從字面意思理解,讓咱們等等。須要等待async2()函數的返回,同時會阻塞後面的代碼。因此,第3行填入 async2。
  • 講道理,await都執行完了,該輪到console.log( 'async1 end' )的輸出了。可是,別忘了下面還有個Promise,有一點須要注意的是:當 new 一個 Promise的時候,其 resolve 方法中的代碼會當即執行。若是不是 async1()的 await 橫插一槓,promise1 能夠排得更前面。因此,如今第4行填入 promise1。
  • 再接下來,同步任務 console.log( 'script end' ) 執行。第5行填入 script end。
  • 還有第6和第7行,未填。回顧一下上面提到 async/await 的概念,其目的是爲了讓異步能像同步同樣地書寫。那麼,我認爲 console.log( 'async1 end' ) 就是個同步任務。因此,第6行填入async1 end。
  • 最後,瓜熟蒂落地在第7行填入 promise2。

與做者答案的不一樣

回過頭對比與做者的答案,發現第6和第7行的順序有問題。babel

再耐心地往下看文章,反覆地看了幾遍 async1 end 和 promise2 誰先誰後,仍是沒法理解爲什麼在chrome瀏覽器中,promise2 會先於 async1 end 輸出。異步

而後,看到評論區,發現也有人提出了相同的疑惑。@rhinel提出,在他的72.0.3622.0(正式版本)dev(64 位)的chrome中,跑出來的結果是 async1 end 在 promise2 以前。async

隨即我想到了一種可能,JS的規範可能會在將來有變化。因而,我用本身的react工程試了一下(工程中的babel-loader版本爲7.1.5。.babelrc的presets設置了stage-3),結果與個人理解一致。當前的最新版本 chromeV71,在這裏的執行順序上,的確存在有問題。

因而,我也在評論區給做者留了言,進行了討論。@rhinel最後也證明,其實最近才發佈經過了這個順序的改進方案,這篇 《Faster async functions and promises》 詳細解釋了這個改進,以及實現效果。不久以後,做者也在他文章的最後,補充了咱們討論的結果,供讀者參考。

總結

最後,我想說的是,本文雖然只是由一道面試題引伸出的,對瀏覽器執行順序的思考、討論與驗證的過程。但正是由於有了這些過程,才讓更多的思想得以碰撞,概念進一步得以理解,規範得以明瞭。

有機會的話,但願能有與更多的同道,多多交流。

更新

講道理,async/await 已經出來挺久了,但在近期的面試中,凡是問及異步操做,面試者的回答都仍是 Promise,甚至知道 async/await 都不多,看來還有待進一步普及。並非說 Promise 有什麼很差,只是以爲 async/await 用着挺爽的,但願能有更多的人用吧。只有用了,才能進一步理解,產生更多的思考。

因此,這兩天翻出了以前寫的關於什麼是async函數,及其相較於 Promise 的優點。從新整理了一下,原文請前往《細說async/await相較於Promise的優點》

但願對你有幫助,也期待進一步的交流,感謝!

PS:歡迎關注個人公衆號 「超哥前端小棧」,交流更多的想法與技術。

圖片描述

相關文章
相關標籤/搜索