Event loop 在瀏覽器端與NodeJS中的差異 以及關於setTimeout與setImmediate引起的問題

組內每週都會有分享總結會,昨晚分享的課題是Event Loop,我很積極且有點自信地答了幾道題,結果被虐的體無完膚,果真有些東西不常常回顧就容易忘,因而花一夜挑燈夜戰從新作了一份有關event loop的知識總結,在此分享給你們,但願對各位看官有所幫助,看完有收穫的同窗還請積極點贊,講的不對的地方,望指出,我會及時修正,謝謝~javascript

瀏覽器端

瀏覽器端的event loop基於javascript中的堆/棧/任務隊列,任務隊列又分爲宏任務微任務java

每次事件循環的時候:
  • 微任務/宏任務在相同做用域下,會先執行微任務,再執行宏任務
  • 宏任務處於微任務做用域下,會先執行微任務,再執行微任務中的宏任務
  • 微任務處於宏任務做用域下時,會先執行宏任務隊列中的任務,而後再執行微任務隊列中的任 務,在當前的微任務隊列沒有執行完成時,是不會執行下一個宏任務的。

本文主要講解的仍是Node,對於瀏覽器端event loop的具體分析及證實能夠查看這篇文章探究javascript中的堆/棧/任務隊列與併發模型 event loop的關係node

Nodejs端

nodejs 的事件循環分爲6個階段,每一個階段都有1個任務隊列,微任務在事件循環的各個階段之間執行

image

timers 階段: 這個階段執行timer(setTimeout、setInterval)的回調

一個timer指定一個下限時間而不是準確時間,在達到這個下限時間後執行回調。在指定的時間事後,timers會盡早的執行回調,可是系統調度或者其餘回調的執行可能會延遲它們。git

從技術上來講,poll階段控制timers何時執行,而執行的具體位置在timers。 下限的時間有一個範圍:[1, 2147483647],若是設定的時間不在這個範圍,將被設置爲1。github

I/O callbacks 階段:執行大多回調以及一些系統調用錯誤,好比網絡通訊的錯誤回調

idle, prepare 階段: 僅node內部使用

poll 階段:獲取新的I/O事件, 適當的條件下node將阻塞在這裏

poll階段有兩個主要的功能:
1. 是執行下限時間已經達到的timers的回調
2. 是處理poll隊列裏的事件。promise

注:Node不少API都是基於事件訂閱完成的,這些API的回調應該都在poll階段完成。 當事件循環進入poll階段:瀏覽器

  • poll隊列不爲空的時候,事件循環確定是先遍歷隊列並同步執行回調,直到隊列清空或執行回調數達到系統上限。網絡

  • poll隊列爲空的時候,這裏有兩種狀況。併發

    • 若是代碼已經被setImmediate()設定了回調,那麼事件循環直接結束poll階段進入check階段來執行check隊列裏的回調。socket

    • 若是代碼沒有被設定setImmediate()設定回調:

      • 若是有被設定的timers,那麼此時事件循環會檢查timers,若是有一個或多個timers下限時間已經到達,那麼事件循環將繞回timers階段,並執行timers的有效回調隊列。
      • 若是沒有被設定timers,這個時候事件循環是阻塞在poll階段等待回調被加入poll隊列。

check 階段:執行 setImmediate() 的回調

這個階段容許在poll階段結束後當即執行回調。若是poll階段空閒,而且有被setImmediate()設定的回調,那麼事件循環直接跳到check執行而不是阻塞在poll階段等待回調被加入。

注:事件循環運行到check階段的時候,setImmediate()具備最高優先級,只要poll隊列爲空,代碼被setImmediate(),不管是否有timers達到下限時間,setImmediate()的代碼都先執行

close callbacks 階段:執行 socket 的 close 事件回調

若是一個sockethandle被忽然關掉(好比socket.destroy()),close事件將在這個階段被觸發,不然將經過process.nextTick()觸發。

NodeJS中關於setTimeout與setImmediate引起的問題

問題引入

setTimeout(()=>{
    console.log('timer')
})
setImmediate(()=>{
    console.log('immediate')
})
複製代碼

能夠發現結果存在隨機性

緣由

首先進入的是timers階段,若是咱們的機器性能通常,那麼進入timers階段,一毫秒已通過去了,那麼setTimeout的回調會首先執行。

若是沒有到一毫秒,那麼在timers階段的時候,下限時間沒到,setTimeout回調不執行,事件循環來到了poll階段,這個時候隊列爲空,此時有代碼被setImmediate(),因此進入check階段,先執行了setImmediate()的回調函數,以後在下一個事件循環再執行setTimemout的回調函數。

而咱們在執行代碼的時候,進入timers的時間延遲實際上是隨機的,並非肯定的,因此會出現兩個函數執行順序隨機的狀況。

咱們再來看一段代碼

fs.readFile('./main.js',()=>{
    setTimeout(()=>{
        console.log('timer')
    })
    setImmediate(()=>{
        console.log('immediate')
    })
})
複製代碼

能夠發現 setImmediate永遠先於 setTimeout執行

緣由

fs.readFile的回調是在poll階段執行的,回調執行完畢後poll階段的隊列爲空,因而進入check階段,執行setImmediate回調,而setTimeout的回調須要等到下一個事件循環的timers階段纔去執行

NodeJS中的process.nextTick() and Promise

對於這兩個,咱們能夠把它們理解成一個微任務。也就是說,它其實不屬於事件循環的一部分。

那麼他們是在何時執行呢?

無論在什麼地方調用,他們都會在其所處的事件循環最後,在事件循環進入下一個循環的階段前執行,可是nextTick優先於promise執行。 process.nextTick()會在各個事件階段之間執行,一旦執行,要直到nextTick隊列被清空,纔會進入到下一個事件階段,因此若是遞歸調用 process.nextTick()/promise,會致使出現I/O starving(飢餓)的問題,推薦使用setImmediate()

看了這麼多,如今你們作兩道題吧,檢測本身是否真的理解了

question1

setTimeout(() => {
  console.log('timeout1')
  Promise.resolve().then(()=>{
        console.log('reslove1')
    })
}, 0)

setTimeout(() => {
  console.log('timeout2')
  Promise.resolve().then(()=>{
        console.log('reslove2')
    })
}, 0)

setImmediate(()=>{
    console.log('setImmediate1')
})

setImmediate(()=>{
    console.log('setImmediate2')
})

複製代碼

question2

setTimeout(() => {
  console.log('timeout1')
  Promise.resolve().then(()=>{
        console.log('reslove1')
    })
}, 0)

setTimeout(() => {
  console.log('timeout2')
  Promise.resolve().then(()=>{
        console.log('reslove2')
    })
}, 0)

setImmediate(()=>{
    console.log('setImmediate1')
})

setImmediate(()=>{
    console.log('setImmediate2')
})

Promise.resolve('resolve3').then((data)=>{
    console.log(data)
})
複製代碼

篇幅很長,很是感受你看完了個人文章,謝謝~,答案會公佈在issue

相關文章
相關標籤/搜索