組內每週都會有分享總結會,昨晚分享的課題是Event Loop
,我很積極且有點自信地答了幾道題,結果被虐的體無完膚
,果真有些東西不常常回顧就容易忘,因而花一夜挑燈夜戰
從新作了一份有關event loop
的知識總結,在此分享給你們,但願對各位看官有所幫助,看完有收穫的同窗還請積極點贊,講的不對的地方,望指出,我會及時修正,謝謝~javascript
瀏覽器端的event loop基於javascript中的
堆/棧/任務隊列
,任務隊列又分爲宏任務
與微任務
java
相同做用域下
,會先執行微任務,再執行宏任務宏任務處於微任務做用域下
,會先執行微任務,再執行微任務中的宏任務微任務處於宏任務做用域下時
,會先執行宏任務隊列中的任務,而後再執行微任務隊列中的任 務,在當前的微任務隊列沒有執行完成時,是不會執行下一個宏任務的。本文主要講解的仍是Node,對於瀏覽器端event loop的具體分析及證實能夠查看這篇文章探究javascript中的堆/棧/任務隊列與併發模型 event loop的關係node
6個階段
,每一個階段都有1個任務隊列,微任務在事件循環的各個階段之間執行一個timer
指定一個下限時間而不是準確時間,在達到這個下限時間後執行回調。在指定的時間事後,timers
會盡早的執行回調,可是系統調度或者其餘回調的執行可能會延遲它們。git
從技術上來講,
poll
階段控制timers
何時執行,而執行的具體位置在timers
。 下限的時間有一個範圍:[1, 2147483647],若是設定的時間不在這個範圍,將被設置爲1。github
poll階段有兩個主要的功能:
1. 是執行下限時間已經達到的timers的回調
2. 是處理poll隊列裏的事件。promise
注:Node不少API都是基於
事件訂閱
完成的,這些API的回調應該都在poll
階段完成。 當事件循環進入poll階段:瀏覽器
poll隊列不爲空的時候,事件循環確定是先遍歷隊列並同步執行回調,直到隊列清空或執行回調數達到系統上限。網絡
poll隊列爲空的時候,這裏有兩種狀況。併發
若是代碼已經被setImmediate()設定了回調,那麼事件循環直接結束poll階段進入check階段來執行check隊列裏的回調。socket
若是代碼沒有被設定setImmediate()設定回調:
這個階段容許在poll階段結束後當即執行回調。若是poll階段空閒,而且有被setImmediate()設定的回調,那麼事件循環直接跳到check執行而不是阻塞在poll階段等待回調被加入。
注:事件循環運行到
check
階段的時候,setImmediate()
具備最高優先級,只要poll
隊列爲空,代碼被setImmediate()
,不管是否有timers
達到下限時間,setImmediate()
的代碼都先執行
若是一個socket
或handle
被忽然關掉(好比socket.destroy()
),close事件將在這個階段被觸發,不然將經過process.nextTick()
觸發。
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
階段纔去執行
對於這兩個,咱們能夠把它們理解成一個微任務。也就是說,它其實不屬於事件循環的一部分。
那麼他們是在何時執行呢?
無論在什麼地方調用,他們都會在其所處的事件循環最後,在事件循環進入下一個循環的階段前執行,可是
nextTick
優先於promise
執行。process.nextTick()
會在各個事件階段之間執行,一旦執行,要直到nextTick
隊列被清空,纔會進入到下一個事件階段,因此若是遞歸調用process.nextTick()
/promise
,會致使出現I/O starving(飢餓)的問題,推薦使用setImmediate()
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')
})
複製代碼
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