參考:https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/7 前端
請寫出下面代碼的運行結果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');
我當時寫的答案是:github
正確答案是:promise
說明我仍是沒有真正理解它們的執行順序。因而看着大牛寫的答案來學習。瀏覽器
首先咱們須要明白如下幾件事情:異步
每一個任務都有一個任務源(task source),源自同一個任務源的task必須放到同一個任務隊列,從不一樣源來的則被添加到不一樣隊列。setTimeout/Promise等API就是任務源。async
每次執行棧執行的代碼就是一個宏任務(包括每次從事件隊列中獲取一個事件回調並放到執行棧中執行)函數
瀏覽器爲了可以使得JS內部macro task與DOM任務可以有序的執行,會在一個macro task執行結束後,在下一個macro task執行開始前,對頁面進行從新渲染。流程以下:post
(macro)task -> 渲染 -> (macro)task ->...學習
(macro)task主要包含:script總體代碼、setTimeout、setInterval、I/O、UI交互事件、postMessage、MessageChannel、setImmediate(Node.js環境)
微任務 (microtask)
在當前task執行結束後當即執行的任務。也就是說,在當前task任務後,下一個task以前,在渲染以前
因此它的響應速度相比setTimeout會更快,由於無需等渲染。也就是說,在某一個macrotask執行完後,就會將在它執行期間產生的全部microtask都執行完畢(在渲染前)
microtask主要包含:Promise.then、MutationObserver、process.nextTick(Node.js環境)
寫在Promise中的代碼被當作同步任務當即執行。而在async/await中,在出現await以前,其中的代碼也是當即執行。
await是一個讓出線程的標誌。await後面的表達式會先執行一遍,將await後面的代碼加入到microtask中,而後就會跳出整個async函數來執行後面的代碼。
await後面的代碼是microtask
async function async1() { console.log('async1 start') await async2() console.log('async1 end') }
等價於
async function async1() { console.log('async1 start') Promise.resolve(async2()).then(() => { console.log('async1 end') }) }
以上就是本道題涉及到的全部相關知識點了。下面咱們再回到這道題來一步一步看看怎麼回事
1.首先,事件循環從宏任務隊列開始,這個時候,宏任務隊列中,只有一個script總體代碼任務,當遇到任務源時,則會先分發任務到對應的任務隊列中去。
2.而後咱們看到首先定義了兩個async函數,而後遇到了console語句,直接輸出script start.輸出以後,script任務繼續往下執行,遇到setTimeout,其做爲一個宏任務源,則會先將其任務分發到對應的隊列中
3.script任務繼續往下執行,執行了async1()函數,輸出async1 start,遇到await時,會將await後面的表達式執行一遍,因此緊接着輸出async2,而後將await後面的代碼也就是console.log('async1 end')加入到microtask中的Promise隊列中。接着跳出async1函數來執行後面的代碼
4.接着遇到Promise實例。因爲Promise中的函數時當即執行的,然後續的.then則會被分發到microtask的Promise隊列中。因此會先輸出promise1, 而後執行resolve,將promise2分配到對應隊列。
5.最後只有一句輸出了script end,至此,全局任務就執行完畢了。
根據上述,每次執行完一個宏任務以後,回去檢查是否存在Microtask,若是有,則執行Microtasks直至清空Microtask Queue
於是再script任務執行完畢以後,開始查找清空微任務隊列。此時,微任務中,Promise隊列有的兩個任務async1 end和promise2,所以按前後順序輸出。當全部的Microtasks執行完畢以後,表示第一輪的循環就結束了。
6.第二輪循環依舊從宏任務隊列開始,此時宏任務中只有一個setTimeout,取出直接輸出便可,至此整個流程結束。
最後原答案做者又加了三個變式用來加深印象,很是好。
在第一個變式中將async2中的函數也變成Promise函數,代碼以下:
async function async1() { console.log('async1 start'); await async2(); console.log('async1 end'); } async function async2() { //async2作出以下更改: new Promise(function(resolve) { console.log('promise1'); resolve(); }).then(function() { console.log('promise2'); }); } console.log('script start'); setTimeout(function() { console.log('setTimeout'); }, 0) async1(); new Promise(function(resolve) { console.log('promise3'); resolve(); }).then(function() { console.log('promise4'); }); console.log('script end');
這一次我寫的答案和做者給出的答案同樣,以下:
async function async1() { console.log('async1 start'); await async2(); //更改以下: setTimeout(function() { console.log('setTimeout1') },0) } async function async2() { //更改以下: setTimeout(function() { console.log('setTimeout2') },0) } console.log('script start'); setTimeout(function() { console.log('setTimeout3'); }, 0) async1(); new Promise(function(resolve) { console.log('promise1'); resolve(); }).then(function() { console.log('promise2'); }); console.log('script end');
這道題我給出的答案是:
正確答案是:
在輸出爲promise2以後,接下來會按照加入setTimeout隊列的順序來依次輸出,經過代碼咱們能夠看到加入順序爲3 2 1,因此會按3,2,1的順序來輸出。
async function a1 () { console.log('a1 start') await a2() console.log('a1 end') } async function a2 () { console.log('a2') } console.log('script start') setTimeout(() => { console.log('setTimeout') }, 0) Promise.resolve().then(() => { console.log('promise1') }) a1() let promise2 = new Promise((resolve) => { resolve('promise2.then') console.log('promise2') }) promise2.then((res) => { console.log(res) Promise.resolve().then(() => { console.log('promise3') }) }) console.log('script end')
答案: