先看下面的例子node
console.log('script start');
async function async1(){
console.log('async1 start');
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');
})
console.log('script end');
複製代碼
執行結果以下:git
script start
async1 start
async2 end
Promise
script end
promise1
async1 end
setTimeout
複製代碼
注意:在新的瀏覽器中打印的結果不是這樣,是下面的樣子github
爲何會不同? 咱們先簡單說一下是v8團隊借鑑了node8中的一個Bug,爲了提高性能,在引擎底層將三次tick減小到了2次。可是這樣作違反了規範,不過規範也是人制定的,是能夠在必定狀況下,修改的。傳送門,想了解的能夠看這裏promise
咱們仍是按照規範理解上面的打印結果
- 1.首先打印出script start (這個就不講了)
- 2.執行到async1()的時候,首先會打印出async1 start,由於async表達式定義的函數也是當即執行的
- 3.而後執行到await async2(),發現saync2也是個async定義的函數,因此直接執行 async2 end,同時async2返回一個Promise,重點:此時返回的Promise會被放入到回調隊列中等待,await會讓出線程(js是單線程的),接下來跳出async1函數,繼續往下執行;
- 4.而後執行到setTimeout,setTimeout是宏任務,會等到執行棧(調用棧)清空以後,微任務所有執行完畢以後,纔會去執行,因此setTimeout會被掛起,最後執行
- 5.而後到了new Promise,new Promise是當即執行的,因此會當即打印出Promise;(Promise是一個當即執行的函數,可是它的成功(resolve())或失敗(reject())回調函數確實一個異步執行的回調。) 而後執行resolve()的時候,resolve()這個任務會被放入回調隊列中,等到調用棧有空閒的時候,事件循環(Event Loop)再來取走它, 這時候會跳出Promise,繼續往下走
- 6.輸出script end;
- 7.同步任務已經所有執行完畢,執行棧如今已經空閒出來,那麼事件循環就會去回調隊列中取任務繼續放到執行棧中執行;
- 8.這時候取的第一個任務就是async2放進去的Promise,執行Promise的時候遇到了resolve函數,resolve又回被放入到任務隊列中繼續等待,而後再次跳出async1 繼續下一個任務
- 9.接下來取走的就是new Promise放進去的resolve回調,這個被調用棧執行,並輸出promise1,而後繼續取下一個任務
- 10.此次終於取到了那麼Promise的resolve回調,由於async2並無return內容,因此這個resolve的參數是undefined,此時await定義的Promise已經執行完畢而且返回告終果,因此能夠繼續往下執行async1函數後面的任務了,因而輸出async1 end
- 11.上面的微任務執行完畢以後,開始執行宏任務中的異步setTimeout
總結:Event Loop 執行順序以下:
- 1 首先執行同步任務,這屬於宏任務
- 2.當執行完全部同步任務代碼以後,執行棧爲空,查詢是否有異步代碼須要執行
- 3.執行全部微任務
- 4.當執行完全部微任務後,若是有必要會渲染頁面
- 5.而後開始下一輪的Event Loop,執行宏任務中的異步代碼,也就是setTimeout中的回調函數
宏任務:
- 1.script
- 2.setTimeout
- 3.setInterval
- 4.setImmediate
- 5.I/O
- 6.UI rendering
微任務:
- 1.process.nextTick(node 獨有)
- 2.promise
- 3.MutationObserver