【Node】EventLoop詳解


EventLoop詳解

一、什么是EventLoop?

想要了解event loop我們就要從js的工作原理說起。首先,大家都知道js是單線程的。所謂單線程就是進程中只有一個線程在運行。那么,js為什么是單線程而不是做成多線程的呢?個人理解,js是用來實現瀏覽器與用戶之間的交互的。如果同時要處理用戶點擊,用戶輸入,用戶關閉等操作,瀏覽器無法知道這個時間我到底應該做什么。所以js是從上至下按順序運行下去的

按照單線程的思想,順序執行我們的代碼,那么,如果我們的js中間向后台發送一個ajax請求,就要等到請求等到結果后才會繼續向下執行。如果請求耗時10秒,頁面就要停在這里10秒。這樣的用戶體驗很不好。。。因此,就有了同步任務、異步任務的區別

同步任務和異步任務在js中是如何執行的呢?js的代碼運行會形成一個主線程和一個任務隊列。主線程會從上到下一步步執行我們的js代碼,形成一個執行棧。同步任務就會被放到這個執行棧中依次執行。而異步任務被放入到任務隊列中執行,執行完就會在任務隊列中打一個標記,形成一個對應的事件。當執行棧中的任務全部運行完畢,js會去提取並執行任務隊列中的事件。這個過程是循環進行的,這就是我們今天想要了解的event loop

因為js的event loop機制,所以大家不要認為setTimeOut設置的事件到了延遲時間就是被執行。如果你的執行棧任務沒有被全部執行完,清空。setTimeOut事件執行的時間很有可能是要大於你設置的延時參數

 

二、宏任務和微任務

異步任務之間是有執行優先級的區別的。不同的異步任務會被分為兩類,微任務和宏任務

宏任務: 需要多次事件循環才能執行完,事件隊列中的每一個事件都是一個宏任務。瀏覽器為了能夠使得js內部宏任務與DOM任務有序的執行,會在一個宏任務執行結束后,在下一個宏執行開始前,對頁面進行重新渲染 (task->渲染->task->…)鼠標點擊會觸發一個事件回調,需要執行一個宏任務,然后解析HTML

 

微任務: 微任務是一次性執行完的。微任務通常來說是需要在當前task執行結束后立即執行的任務,例如對一些動作做出反饋或者異步執行任務又不需要分配一個新的task,這樣便可以提高一些性能。只要執行棧中沒有其他的js代碼正在執行了,而且每個宏任務都執行完了,微任務隊列會立即執行。如果在微任務執行期間微任務隊列加入了新的微任務,會將新的微任務加入隊列尾部,之后也會被執行。簡單理解,宏任務在下一輪事件循環執行,微任務在本輪事件循環的所有任務結束后執行

在一個事件循環中,異步事件返回結果后會被放到一個任務隊列中。然而,根據這個異步事件的類型,這個事件實際上會被對應的宏任務隊列或者微任務隊列中去。並且在當前執行棧為空的時候,主線程會 查看微任務隊列是否有事件存在。如果不存在,那么再去宏任務隊列中取出一個事件並把對應的回到加入當前執行棧;如果存在,則會依次執行隊列中事件對應的回調,直到微任務隊列為空,然后去宏任務隊列中取出最前面的一個事件,把對應的回調加入當前執行棧...如此反復,進入循環

 

宏任務:

包括整個代碼script( script中的代碼都屬於宏任務 )

setTimeout

setInterval

微任務:

promise.then()

process.nextTick

事件執行的順序:

先執行宏任務,然后執行微任務

三、案例

console.log("script start");
​
setTimeout(function(){
   console.log("setTimeout");
},0)
​
newPromise(resolve=>{
   console.log("promise start");
   resolve();
}).then(function(){
   console.log("promise1");
}).then(()=>{
   console.log("promise2");
})
​
console.log("script end");
​
注意promise不算是一個異步任務它是一個同步任務

 

 

console.log(1);
​
setTimeout(()=>{
 console.log(2);
})
​
newPromise((resolve)=>{
 console.log(4)
 resolve()
}).then(()=>{
setTimeout(()=>{
     console.log(5);
  })
}).then(()=>{
 console.log(6)
})
​
console.log(7)
setTimeout(() => {
 console.log(5)
• new Promise(resolve => {
    console.log(6)
•    setTimeout(() => {
      console.log(7)
    })
    resolve()
  }).then(() => {
    console.log(8)
  })
}, 500)
new Promise(resolve => {
 console.log(9)
 resolve()
}).then(() => {
 console.log(10)
• setTimeout(() => {
    console.log(11)
  }, 0)
})
•console.log(12)
 

 

四、NodeJS中的EventLoop

其實nodejs與瀏覽器的區別,就是nodejs的 宏任務 分好幾種,而這好幾種又有不同的 任務隊列,而不同的 任務隊列 又有順序區別,而 微任務是穿插在每一種【注意不是每一個!】宏任務 之間的

Timers 類型的宏任務隊列

  • setTimeout()

  • setInterval

Check 類型的宏任務隊列

  • setImmediate()

Close callback 類型的宏任務隊列

  • socket.on(‘close’, () => {})

Poll 類型的宏任務隊列

  • 除了上面幾種的其他所有回調

 

 

五、nodeJs 里面的微任務隊列

process.nextTick() Promise.then()

process.nextTick()的優先級高於所有的微任務,每一次清空微任務列表的時候,都是先執行 process.nextTick()

 

六、NodeJS中的EventLoop與瀏覽器的EventLoop之間的區別

瀏覽器

先執行 一個 宏任務,然后執行所有微任務…...循環往復

NodeJS

先執行一種 宏任務 在執行清空微任務 再一種 宏任務 在執行清空微任務 再一種 宏任務 在執行清空微任務 所有種類宏任務結束 循環往復

分析瀏覽器和NodeJS執行的順序

setTimeout(() =>{
 console.log(‘timer1’)
 Promise.resolve().then(() =>{
   console.log(‘promise1’)
})
}, 0)
​
​
setTimeout(() =>{
 console.log(‘timer2’)
 Promise.resolve().then(() =>{
   console.log(‘promise2’)
})
}, 0)

 

 

七、setTimeout && setImmediate執行順序

Node 並不能保證 timers 在預設時間到了就會立即執行,因為 Node 對 timers 的過期檢查不一定靠譜,它會受機器上其它運行程序影響,或者那個時間點主線程不空閑

雖然 setTimeout 延時為 0,但是一般情況 Node 把 0 會設置為 1ms,所以,當 Node 准備 event loop 的時間大於 1ms 時,進入 timers 階段時,setTimeout 已經到期,則會先執行 setTimeout;反之,若進入 timers 階段用時小於 1ms,setTimeout 尚未到期,則會錯過 timers 階段,先進入 check 階段,而先執行 setImmediate

setTimeout(() =>{
 console.log('timeout')
}, 0)
​
setImmediate(() =>{
 console.log('immediate')
})

 

 

有一種情況,它們兩者的順序是固定的

constfs=require('fs')
​
fs.readFile('test.txt', () =>{
 console.log('readFile')
 setTimeout(() =>{
   console.log('timeout')
}, 0)
 setImmediate(() =>{
   console.log('immediate')
})
})

 

此時 setTimeout 和 setImmediate 是寫在 I/O callbacks 中的,這意味着,我們處於 poll 階段,然后是 check 階段,所以這時無論 setTimeout 到期多么迅速,都會先執行 setImmediate。本質上是因為,我們從 poll 階段開始執行

 

總結

  1. 當 setTimeout() 和 setImmediate() 都寫在 main 里面的時候 不一定誰先執行誰后執行

  2. 當 setTimeout() 和 setImmediate() 都寫在一個 I/O 回調 或者說一個 poll 類型宏任務的回調里面的時候 一定是先執行 setImmediate() 后執行 setTimeout()

 

八、Poll 階段的兩個主要功能

setImmediate 的 queue 不為空,則進入 check 階段,然后是 close callbacks 階段……

setImmediate 的 queue 為空,但是 timers 的 queue 不為空,則直接進入 timers 階段,然后又來到 poll 階段……


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM