[JavaScript]js EventLoop And Async

update 2019-07-03html

What

js事件循環前端

先來看一張圖(這張圖來自於http://www.zcfy.cc/article/node-js-at-scale-understanding-the-node-js-event-loop-risingstack-1652.html)node

js事件循环

再來看一張圖(這張圖來自於https://www.cnblogs.com/hity-tt/p/6733062.html)
web


從一開始,js有一個執行棧(stack)中存放了該頁面全部的代碼即該頁面主線程,當執行遇到異步操做(async)時,瀏覽器的webcore模塊(如network,timer,domBinding模塊等)將其放置到一個幕後線程中等待,而後瀏覽器接着執行主線程,當幕後線程中的代碼準備好了(如按期器時間到了,請求的響應來了),該線程就會把這個函數的回調放到taskQueue(任務隊列)中等待,當主線程的執行棧所有執行完畢時(全部同步操做所有執行完畢),主線程對任務對列進行檢測是否有任務要執行,若是有,就把該任務放到執行棧中進行,若是沒有,就保持等待(循環檢測)任務到來。promise

上述的過程,就是事件循環(EventLoop)。瀏覽器

Why need EventLoop 

由於web前端端用戶的豐富交互性問題,js這邊採起了單線程來應對多個交互產生時的程序操做複雜性問題。因爲單線程的出現,當同步操做出現長時間等待時就會出現線程阻塞,爲了解決阻塞,出現了異步操做(變相多線程)來解決線程阻塞。bash

異步操做就是經過EventLoop的實現的數據結構

SimpleLook

事件循環三步看

  1.  判斷js代碼是否爲異步,異步則放置該代碼到幕後線程,同步則繼續執行
  2.  幕後線程中的函數回調被觸發時將該代碼推入任務隊列
  3.  同步任務執行完畢後,檢測事件隊列,將任務隊列尾部函數回調推入執行棧執行
  4.  以上三步循環執行,即EventLoop

兩步來看

  1. 執行主線程,同步操做,遇到異步壓入事件表隨後(該代碼在觸發響應時)壓入隊列
  2. 主線程代碼執行完畢後,無限循環(?)檢測異步事件隊列,有則進入1執行

一句話講清楚

主線程執行完畢後,檢測任務隊列有則取出一個插入主線程,重複該動做。多線程

任務優先級

如上圖,任務隊列中的任務分爲兩種,對應大型工做場景和微處理場景,即對應的傳說中的Macrotask和Microtask。dom

what

顧名思義,即多個任務同時出現時的執行優先等級。

Macrotask: setTimeout,setInterval,setImmediate,用戶交互操做/UI渲染(請求回調執行)etc

Microtask:  process.nextTick(nodejs)>promise.then etc

why

當任務隊列中有多個任務時,事件優先級就須要出現了。

How

大體多任務時處理步驟以下四小步(來自網上,感受該步驟描述有問題,我後續修復了)

1. 抽取任務中全部MicroTask到2

2. 從Microtask隊列中取隊首(在隊列時間最長)的任務去執行棧中執行(僅僅一個),執行完後進入下一步3

3. 檢查Microtask隊列是否爲空,空則到4,不然到2

4. 檢測Macrotask中的MicroTask,非空1, 空5

5. 從Macrotask隊列中取隊尾(在隊列時間最長)的任務進去執行棧執行,執行完後,跳到4


Important

在一個循環中,或者說在處理完執行棧後,在處理任務隊列時,永遠先處理微任務隊列。換句話說,若是在當前循環中一個微任務中出現了一個異步微任務,那麼該微任務屬於本次循環,會被推入微任務隊列中!你仍然須要優先將while (microtask.chain.length) { // }執行下去。直到微任務處理完畢。再執行宏任務。至於同次循環中多個微任務執行時機,視推入時機而定!


簡而言之:同步環境執行 -> 事件循環1(microtask queue的All)-> 事件循環2(macrotask queue中的一個) -> 事件循環1(microtask queue的All)-> 事件循環2(macrotask queue中的一個)...

通俗點說任務隊列中的任務所執行的順序:

開始以前簡單講下隊列數據結構,一種先進先出的數據結構,能夠理解爲一個雙開的口徑爲一個豆子大小的竹筒,隊列中的數據就像豆子,豆子們排着隊從竹筒的一頭倒進去再從另外一頭倒出來,就是這麼理解。

  • 首先清楚一個概念,用大小豆子類來比喻優先級,宏任務表明着大豆子子類,微任務表明着小豆子子類。
  • 系統將任務隊列的任務豆子們按照在任務隊列中的前後順序(豆子們被依次塞入竹筒,越靠近出口處序號越大)升序寫上編號後從竹篩中篩出來。
  • 而後先將小豆子們按照編號進行升序排序後倒入竹筒中,小豆子們被依次倒出到執行棧,一次倒一個(最後一個出來的小豆子序號最小咯)。
  • 再將大豆子們按照編號升序排序後倒入竹筒中,大豆子們被依次倒出到執行棧。
  • 這樣就完成了一次倒豆子!(任務隊列執行)
  • 若是豆子們中出現了新的豆子(新的異步任務)怎麼辦呢?(從這一條能夠不看,看上面有排序的處理步驟就行了)
  • 若是是小豆子,那麼將持續的將小豆子先倒光再去倒大豆子。
  • 接下來再去按上面的順序處理大豆子。

看完上面後來理解下下面的代碼(基於掘金小美娜娜登峯造極版修改,添加Promise鏈式調用第二層then)順便理解下上面說的微任務處理器的清空處理是什麼意思,理解後相信對事件循環理解更深了。

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");
}).then(()=>{console.log('promise3')});
console.log('script end'); /* 
script start
VM955:2 async1 start
VM955:8 async2
VM955:20 promise1
VM955:25 script end
VM955:4 async1 end
VM955:23 promise2
VM955:24 promise3
undefined
VM955:14 settimeout
*/複製代碼

我知道你看到這有點暈,回過頭去看看Important吧。

原理的話,我這兩天會把Promise以及用於鏈式的then方法實現一下,能夠來看看而後再看上面這個例子。

Special


Demo

demo來源於網上,作抽象概念具現化理解用

setTimeout(function(){console.log('111')},0);
new Promise(function(resolve,reject){
console.log("2222");//此處尚未執行異步操做,執行異步操做及執行回調函數,在promise中即then中的回調
resolve();
}).then(function(){console.log('3333')})
console.log("44444");
//輸出
// 2222
// 44444//上面的兩個輸出屬於同步操做
// 3333//promise加入到隊列的優先級高於setTimeout
//111

嵌套微任務

new Promise(function(resolve,reject){

resolve();
}).then( function (){
console.log( "111" );
return new Promise( function (resolve,reject){
resolve();
})
}).then( function (){ console.log( "222" );})
new Promise( function (resolve,reject){
resolve();
}).then( function (){ console.log( "33333" );})
//輸出
111 33333 222


參考

Eventloop不可怕,可怕的是趕上Promise

相關文章
相關標籤/搜索