總結事件輪詢機制,以及宏任務隊列與微任務隊列
這篇博文僅爲我的理解,文章內提供一些更加權威的參考,若有片面及錯誤,歡迎指正java
1. 事件輪詢(Event Loop)
事件輪詢(Event Loop) - 《你不懂JS:異步與性能》promise
【推薦】詳解JavaScript中的Event Loop(事件循環)機制瀏覽器
Javascript的宿主環境中共通的一個「線程」(一個「不那麼微妙」的異步玩笑,無論怎樣)是,他們都有一種機制:在每次調用JS引擎時,能夠隨着時間的推移執行你的程序的多個代碼塊兒,這稱爲「事件輪詢(Event Loop)」。markdown
換句話說,JS引擎對 時間 沒有天生的感受,反而是一個任意JS代碼段的按需執行環境。是它周圍的環境在不停地安排「事件」(JS代碼的執行)。app
js實現異步的具體解決方案
- 同步代碼直接執行
- 異步函數到了指定時間再放到異步隊列
- 同步執行完畢,異步隊列輪詢執行。
什麼叫輪詢?
精簡版:當第一個異步函數執行完以後,再到異步隊列監視。一直不斷循環往復,因此叫事件輪詢。異步
詳細版:js引擎遇到一個異步事件後並不會一直等待其返回結果,而是會將這個事件掛起,繼續執行執行棧中的其餘任務。當一個異步事件返回結果後,js會將這個事件加入與當前執行棧不一樣的另外一個隊列,咱們稱之爲事件隊列。被放入事件隊列不會馬上執行其回調,而是等待當前執行棧中的全部任務都執行完畢, 主線程處於閒置狀態時,主線程會去查找事件隊列是否有任務。若是有,那麼主線程會從中取出排在第一位的事件,並把這個事件對應的回調放入執行棧中,而後執行其中的同步代碼…,如此反覆,這樣就造成了一個無限的循環。這就是這個過程被稱爲「事件循環(Event Loop)」的緣由。async
事實上,事件輪詢與宏任務和微任務密切相關。
2. 宏任務和微任務
概念
在一個事件循環中,異步事件返回結果後會被放到一個任務隊列中。然而,根據這個異步事件的類型,這個事件實際上會被對應的宏任務隊列或者微任務隊列中去。而且在當前執行棧爲空的時候,主線程會 查看微任務隊列是否有事件存在。若是不存在,那麼再去宏任務隊列中取出一個事件並把對應的回到加入當前執行棧;若是存在,則會依次執行隊列中事件對應的回調,直到微任務隊列爲空,而後去宏任務隊列中取出最前面的一個事件,把對應的回調加入當前執行棧…如此反覆,進入循環。
咱們只需記住噹噹前執行棧執行完畢時會馬上先處理全部微任務隊列中的事件,而後再去宏任務隊列中取出一個事件。同一次事件循環中,微任務永遠在宏任務以前執行。
在當前的微任務沒有執行完成時,是不會執行下一個宏任務的。
因此就有了那個常常在面試題、各類博客中的代碼片斷:
setTimeout(_ => console.log(4)) new Promise(resolve => { resolve() console.log(1) }).then(_ => { console.log(3) }) console.log(2)
setTimeout就是做爲宏任務來存在的,而Promise.then則是具備表明性的微任務,上述代碼的執行順序就是按照序號來輸出的。
全部會進入的異步都是指的事件回調中的那部分代碼
也就是說new Promise在實例化的過程當中所執行的代碼都是同步進行的,而then中註冊的回調纔是異步執行的。
在同步代碼執行完成後纔回去檢查是否有異步任務完成,並執行對應的回調,而微任務又會在宏任務以前執行。
因此就獲得了上述的輸出結論一、二、三、4。
+部分表示同步執行的代碼
+setTimeout(_ => { - console.log(4) +}) +new Promise(resolve => { + resolve() + console.log(1) +}).then(_ => { - console.log(3) +}) +console.l
原本setTimeout已經先設置了定時器(至關於取號),而後在當前進程中又添加了一些Promise的處理(臨時添加業務)。
因此進階的,即使咱們繼續在Promise中實例化Promise,其輸出依然會早於setTimeout的宏任務:如EXP2
宏任務
分類:
# | 瀏覽器 | Node |
---|---|---|
I/O | ✅ | ✅ |
setTimeout | ✅ | ✅ |
setInterval | ✅ | ✅ |
setImmediate | ❌ | ✅ |
requestAnimationFrame | ✅ | ❌ |
特性:
-
宏任務所處的隊列就是宏任務隊列
-
第一個宏任務隊列中只有一個任務:執行主線程上的JS代碼;若是遇到上方表格中的異步任務,會建立出一個新的宏任務隊列,存放這些異步函數執行完成後的回調函數。
-
宏任務隊列能夠有多個
-
宏任務中能夠建立微任務,可是在宏任務中建立的微任務不會影響當前宏任務的執行。(EXP3)
-
當一個宏任務隊列中的任務所有執行完後,會查看是否有微任務隊列,若是有就會優先執行微任務隊列中的全部任務,若是沒有就查看是否有宏任務隊列
微任務
分類:
# | 瀏覽器 | Node |
---|---|---|
process.nextTick | ❌ | ✅ |
MutationObserver | ✅ | ❌ |
Promise.then catch finally | ✅ | ✅ |
特性:
-
微任務所處的隊列就是微任務隊列
-
在上一個宏任務隊列執行完畢後,若是有微任務隊列就會執行微任務隊列中的全部任務
-
new promise((resolve)=>{ 這裏的函數在當前隊列直接執行 }).then( 這裏的函數放在微任務隊列中執行 )
-
微任務隊列上建立的微任務,仍會阻礙後方將要執行的宏任務隊列 (EXP2)
-
由微任務建立的宏任務,會被丟在異步宏任務隊列中執行 (EXP4)
例題
EXP1: 在主線程上添加宏任務與微任務
執行順序:主線程 => 主線程上建立的微任務 => 主線程上建立的宏任務
console.log('-------start--------'); setTimeout(() => { console.log('setTimeout'); // 將回調代碼放入另外一個宏任務隊列 }, 0); new Promise((resolve, reject) => { for (let i = 0; i < 5; i++) { console.log(i); } resolve() }).then(()=>{ console.log('Promise實例成功回調執行'); // 將回調代碼放入微任務隊列 }) console.log('-------e
結果:
-------start-------- 0 1 2 3 4 -------end-------- Promise實例成功回調執行 setTimeout
由EXP1,咱們能夠看出,當JS執行完主線程上的代碼,會去檢查在主線程上建立的微任務隊列,執行完微任務隊列以後纔會執行宏任務隊列上的代碼
EXP2: 在微任務中建立微任務
執行順序:主線程 => 主線程上建立的微任務1 => 微任務1上建立的微任務2 => 主線程上建立的宏任務
setTimeout(_ => console.log(4)) new Promise(resolve => { resolve() console.log(1) }).then(_ => { console.log(3) Promise.resolve().then(_ => { console.log('before timeout') }).then(_ => { Promise.resolve().then(_ => { console.log('also before timeout') }) }) }) console.log(2)
結果:
1 2 3 before timeout also before timeout 4
由EXP1,咱們能夠看出,在微任務隊列執行時建立的微任務,仍是會排在主線程上建立出的宏任務以前執行
EXP3: 宏任務中建立微任務
執行順序:主線程 => 主線程上的宏任務隊列1 => 宏任務隊列1中建立的微任務
// 宏任務隊列 1 setTimeout(() => { // 宏任務隊列 2.1 console.log('timer_1'); setTimeout(() => { // 宏任務隊列 3 console.log('timer_3') }, 0) new Promise(resolve => { resolve() console.log('new promise') }).then(() => { // 微任務隊列 1 console.log('promise then') }) }, 0) setTimeout(() => { // 宏任務隊列 2.2 console.log('timer_2') }, 0) console.log('========== Sync queue ==========') // 執行順序:主線程(宏任務隊列 1)=> 宏任務隊列 2 => 微任務隊列 1 => 宏任務隊列 3
結果:
========== Sync queue ========== timer_1 new promise promise then
timer_2 timer_3
EXP4:微任務隊列中建立的宏任務
執行順序:主線程 => 主線程上建立的微任務 => 主線程上建立的宏任務 => 微任務中建立的宏任務
異步宏任務隊列只有一個,當在微任務中建立一個宏任務以後,他會被追加到異步宏任務隊列上(跟主線程建立的異步宏任務隊列是同一個隊列)
// 宏任務1 new Promise((resolve) => { console.log('new Promise(macro task 1)'); resolve(); }).then(() => { // 微任務1 console.log('micro task 1'); setTimeout(() => { // 宏任務3 console.log('macro task 3'); }, 0) }) setTimeout(() => { // 宏任務2 console.log('macro task 2'); }, 1000) console.log('========== Sync queue(macro task 1) ==========');
結果:
========== Sync queue(macro task 1) ========== micro task 1 macro task 3 macro task 2
記住,若是把
setTimeout(() => { // 宏任務2 console.log('macro task 2'); }, 1000)改成當即執行setTimeout(() => { // 宏任務2 console.log('macro task 2'); }, 0)
那麼它會在macro task 3以前執行,由於定時器是過多少毫秒以後纔會加到事件隊列裏
總結
微任務隊列優先於宏任務隊列執行,微任務隊列上建立的宏任務會被後添加到當前宏任務隊列的尾端,微任務隊列中建立的微任務會被添加到微任務隊列的尾端。只要微任務隊列中還有任務,宏任務隊列就只會等待微任務隊列執行完畢後再執行。
最後上一張幾乎涵蓋基本狀況的例圖和例子
console.log('======== main task start ========'); new Promise(resolve => { console.log('create micro task 1'); resolve(); }).then(() => { console.log('micro task 1 callback'); setTimeout(() => { console.log('macro task 3 callback'); }, 0); }) console.log('create macro task 2'); setTimeout(() => { console.log('macro task 2 callback'); new Promise(resolve => { console.log('create micro task 3'); resolve(); }).then(() => { console.log('micro task 3 callback'); }) console.log('create macro task 4'); setTimeout(() => { console.log('macro task 4 callback'); }, 0); }, 0); new Promise(resolve => { console.log('create micro task 2'); resolve(); }).then(() => { console.log('micro task 2 callback'); }) console.log('======== main task end ========');
結果:
一旦遇到await 就馬上讓出線程,阻塞後面的代碼
等候以後,對於await來講分兩種狀況
- 不是promise 對象
- 是promise對象
若是不是promise,await會阻塞後面的代碼,先執行async外面的同步代碼,同步代碼執行完畢後,在回到async內部,把promise的東西,做爲await表達式的結果
若是它等到的是一個 promise 對象,await 也會暫停async後面的代碼,先執行async外面的同步代碼,等着 Promise 對象 fulfilled,而後把 resolve 的參數做爲 await 表達式的運算結果。
若是一個 Promise 被傳遞給一個 await 操做符,await 將等待 Promise 正常處理完成並返回其處理結果。
具體參考http://www.javashuo.com/article/p-nsxprmww-bq.html,但博客裏的代碼運行結果須要從新試一下
第三題
async function async1() { console.log( 'async1 start' ) await async2() console.log( 'async1 end' ) } async function async2() { console.log( 'async2' ) } async1() console.log( 'script start' ) 執行結果
第四題
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' )
若是一個 Promise 被傳遞給一個 await 操做符,await 將等待 Promise 正常處理完成並返回其處理結果。
仔細看此例子:,區分await後執行promise和非promise的區別,
async function t1 () { console.log(1) console.log(2) new Promise( function ( resolve ) { console.log( 'promise3' ) resolve(); } ).then( function () { console.log( 'promise4' ) } ) await new Promise( function ( resolve ) { console.log( 'b' ) resolve(); } ).then( function () { console.log( 't1p' ) } ) console.log(3) console.log(4) new Promise( function ( resolve ) { console.log( 'promise5' ) resolve(); } ).then( function () { console.log( 'promise6' ) } ) } setTimeout( function () { console.log( 'setTimeout' ) }, 0 ) async function t2() { console.log(5) console.log(6) await Promise.resolve().then(() => console.log('t2p')) console.log(7) console.log(8) } t1() new Promise( function ( resolve ) { console.log( 'promise1' ) resolve(); } ).then( function () { console.log( 'promise2' ) } ) t2() console.log('end');
![](http://static.javashuo.com/static/loading.gif)
記住,await以後的代碼必須等await語句執行完成後(包括微任務完成),才能執行後面的,也就是說,只有運行完await語句,才把await語句後面的所有代碼加入到微任務行列,因此,在遇到await promise時,必須等await promise函數執行完畢才能對await語句後面的所有代碼加入到微任務中,因此,
在等待await Promise.then微任務時,
1.運行其餘同步代碼,
2.等到同步代碼運行完,開始運行await promise.then微任務,
3.await promise.then微任務完成後,把await語句後面的所有代碼加入到微任務行列,
4.根據微任務隊列,先進後出執行微任務
await 語句是同步的,await語句後面所有代碼纔是異步的微任務,
因此: