帶你瞭解NodeJS事件循環

瀏覽器中存在兩個任務隊列,一個是宏任務一個是微任務。但是在NodeJS中一共存在六個事件隊列,timerspending callbacks,idle preparepoll,checkclose callbacks。每一個隊列裡面存放的都是回調函數callback

這六個隊列是按順序執行的。每個隊列負責存儲不同的任務。

  • timer裡面存在的是setTimeout與setInterval的回調函數
  • pending callback是執行操作系統的回調,例如tcp,udp。
  • idle prepare隻在系統內部進行使用。一般開發者用不到
  • poll執行與IO相關的回調操作
  • check中存放setImmediate中的回調。
  • close callbacks執行close事件的回調。

在Node中代碼從上到下同步執行,在執行過程中會將不同的任務添加到相應的隊列中,比如說setTimeout就會放在timers中, 如果遇到文件讀寫就放在poll裡面,等到整個同步代碼執行完畢之後就會去執行滿足條件的微任務。可以假想有一個隊列用於存放微任務,這個隊列和前面的六種沒有任何關系。

當同步代碼執行完成之後會去執行滿足條件的微任務,一旦所有的微任務執行完畢就會按照上面列出的順序去執行隊列當中滿足條件的宏任務。

首先會執行timers當中滿足條件的宏任務,當他將timers中滿足的任務執行完成之後就會去執行隊列的切換,在切換之前會先去清空微任務列表中的微任務。

所以微任務執行是有兩個時機的,第一個時機是所有的同步代碼執行完畢,第二個時機隊列切換前。

註意在微任務中nextTick的執行優先級要高於Promise,這個隻能死記瞭。

setTimeout(() => {
    console.log('s1');
})

Promise.resolve().then(() => {
    console.log('p1');
})

console.log('start');

process.nextTick(() => {
    console.log('tick');
})

setImmediate(() => {
    console.log('st');
})

console.log('end');

// start end tick p1 s1 st
setTimeout(() => {
    console.log('s1');
    Promise.resolve().then(() => {
        console.log('p1');
    })
    process.nextTick(() => {
        console.log('t1');
    })
})

Promise.resolve().then(() => {
    console.log('p2')
})

console.log('start');

setTimeout(() => {
    console.log('s2');
    Promise.resolve().then(() => {
        console.log('p3');
    })
    process.nextTick(() => {
        console.log('t2');
    })
})

console.log('end');

// start end p2 s1 s2 t1 t2 p1 p3

Node與瀏覽器事件環執行是有一些不同的。

首先任務隊列數不同,瀏覽器一般隻有宏任務和微任務兩個隊列,而Node中除瞭微任務隊列外還有6個事件隊列。

其次微任務執行時機不同,不過他們也有相同的地方就是在同步任務執行完畢之後都會去看一下微任務是否存在可執行的。對瀏覽器來說每當一個宏任務執行完成之後就會清空一次微任務隊列。在Node中隻有在事件隊列切換時才會去清空微任務隊列。

最後在Node平臺下微任務執行是有優先級的,nextTick優先於Promise.then, 而瀏覽器中則是先進先出。

setTimeout(() => {
    console.log('timeout');
})

setImmediate(() => {
    console.log('immdieate');
})

在Node中時而會先輸出timeout時而會先輸出immdieate,這是因為setTimeout是需要接收一個時間參數的,如果沒寫就是一個0,我們都知道無論是在Node還是在瀏覽器,程序是不可能真的是0,他會受很多的因素影響。這取決於運行的環境。

如果setTimeout先執行就會放在timers隊列中,這樣timeout就會先輸入,如果setTimeout因為某些原因後執行瞭,那麼check隊列中的immdieate就會先執行。這就是為什麼時而輸出timeout時而輸出immdieate

const fs = require('fs');

fs.readFile('./a.txt', () => {
    setTimeout(() => {
        console.log('timeout');
    }, 0)

    setImmediate(() => {
        console.log('immdieate');
    })
})

這種情況就會一直先輸出immdieate後輸出timeout,這是因為,代碼執行的時候會在timers裡面加入timeout, 在poll中加入fs的回調,在check中加入immdieate。fs的回調執行結束之後實在poll隊列,隊列切換的時候首先會去看微任務,但是這裡沒有微任務就會繼續向下,下面就是check隊列而不是timers隊列,所以poll清空之後會切換到check隊列,執行immdieate回調。

到此這篇關於帶你瞭解NodeJS事件循環的文章就介紹到這瞭,更多相關NodeJS事件循環內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: