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

一篇文章和一道面試題

最近,有篇名爲 《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行的順序有問題。bash

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

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

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

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

總結

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

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

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

相關文章
相關標籤/搜索