之前都是在掘金上看別人的文章,好的點個贊。逛github的時候,也只是喜歡看別人源碼,也從不在git上上傳東西。今天忽然萌發了本身想要發一些東西,方便本身之後查閱,也能讓你們指點一下,是否正確。省得在錯誤的道路上越走越遠,還和正確的人爭論的面紅耳赤。畢竟一旦確立了錯誤的觀點,那麼錯誤的觀點就會在你潛意識裏變成正確的了。javascript
正常狀況下,setTimeout、setInterval、setImmediate和process.nextTick都是異步執行的,那麼這四個函數方法的執行機制和時間究竟是如何的呢,各自有什麼區別呢?可否替換呢?這一切都要從nodejs的event loop上面出發才能 有所理解吧。java
先分別介紹各個函數,setTimeout和setInterval最爲類似,在函數分析上,咱們知道,setTimeout和setInterval的函數格式都是以下:node
setTimeout(function(arg1,arg2){
//some code
},XXX)
setInterval(function(arg1,arg2){
//some code
},XXX)
複製代碼
那麼這個XXX延遲時間是有個規定的,延遲時間的範圍是[1,2^31-1]。當你延遲時間設定小於1或者大於2^31-1的時候,延遲時間默認被修改爲1,即當你寫setTimeout(function(arg1,arg2){},0.1)其實等價於寫了setTimeout(function(arg1,arg2){},1)。git
咱們直接看代碼,這兩個函數的執行結果如何:github
setImmediate(function(){
console.log('immediate')
})
process.nextTick(function(){
console.log('next tick')
})
複製代碼
交換代碼順序
process.nextTick(function(){
console.log('next tick')
})
setImmediate(function(){
console.log('immediate')
})
複製代碼
咱們發現代碼輸出的結果是同樣的。那麼nextTick的執行機制實在setImmediate以前的。
在介紹4個函數的機制以前,咱們來看一個有趣的現象promise
setTimeout(() => {
console.log('setTimeout')
}, 0)
setImmediate(() => {
console.log('setImmediate')
})
複製代碼
輸出的結果時而是異步
setTimeout
setImmediate
複製代碼
時而是函數
setImmediate
setTimeout
複製代碼
爲啥這兩個函數的執行的順序如此不固定呢?難道有隨機性存在嗎? 其實否則,咱們來看一張圖:oop
這是整個event loop的簡略圖,不少東西我都刪減掉了,I/O裏面的細節操做我逗縮寫在一個步驟裏了。 咱們用通俗距離方法來講吧,setTimeout和setInterval的等級是同樣的,因此方法在代碼裏按照前後順序註冊執行。可是按上面代碼輸出,爲何1和3的步驟會出現隨機性輸出呢?setTimeout的回調函數在1階段執行,setImmediate的回調函數在3階段執行。event loop先檢測1階段,這個是正確的,官方文檔也說了The event loop cycle is timers -> I/O -> immediates, rinse and repeat. 可是有個問題就是進入第一個event loop時間不肯定,不必定就是從頭開始進 入的,上面的例子進入的時間並不完整。網上有人總結,當進入event loop的 時間低於1ms,則進入check階段,也就是3階段,調用setImmediate,若是超過1ms,則進入的是timer階段,也就是1階段,回調setTimeout的回調函數。ui
因此4個函數的機制咱們能夠總結了:在1階段(timer階段),咱們註冊的是setTimeout和setInterval回調函數,在I/O階段以後的3階段(check階段),咱們註冊的是setImmediate的回調函數。如今就剩下process.nextTick函數了。這個函數比較特殊,他註冊時間實在上圖中綠色箭頭的tick階段。
const fs = require('fs')
fs.readFile(__filename, () => {
setTimeout(() => {
console.log('setTimeout')
}, 0)
setImmediate(() => {
console.log('setImmediate')
})
})
複製代碼
運行結果:
setImmediate
setTimeout
複製代碼
理由:
timer -- I/O -- check。這三個階段是event loop的執行順序,當fs讀取文件時,咱們已經將setTimeout和setImmediate註冊在event loop中了,當fs文件流讀取完畢,執行到了I/O階段,而後去執行check階段,執行setImmediate的回調函數,而後去下一次輪詢的時候進入到timer階段執行setTimeout。
setInterval(() => {
console.log('setInterval')
}, 100)
process.nextTick(function tick () {
process.nextTick(tick)
})
複製代碼
運行結果:
無任何輸出,setInterval永遠不執行
複製代碼
理由:
由於process.nextTick是註冊在tick階段的,回調的仍然是process.nextTick方法,可是process.nextTick不是註冊在下一個輪詢的tick階段,而是在當前的tick階段進行拼接,繼續執行,從而致使了死循環,event loop根本沒機會進入到timer階段
###題目三
setImmediate(() => { ------ 1
console.log('setImmediate1')
setImmediate(() => { ------2
console.log('setImmediate2')
})
process.nextTick(() => { -------3
console.log('nextTick')
})
})
setImmediate(() => { ------4
console.log('setImmediate3')
})
複製代碼
運行結果:
setImmediate1
setImmediate3
nextTick
setImmediate2
複製代碼
理由:
先將最外層第一個setImmediate,即標號爲1註冊,而後註冊最外層標號爲2的setImmediate。接下來註冊第一個setImmediate裏面的異步函數。先註冊標號爲3的setImmediate的函數,而後註冊標號爲4的process.nextTick。此時進入event loop執行回調,先執行1裏面的函數,輸出setImmediate1,因爲3和4都在2以後註冊的,此時執行的是標號爲4的回調方法,輸出setImmediate3。繼續輪詢,因爲process.nextTick是註冊在4以後的tick中,因此先執行process.nextTick,最好輪詢執行2的回調方法,輸出setImmediate2
const promise = Promise.resolve()
promise.then(() => {
console.log('promise')
})
process.nextTick(() => {
console.log('nextTick')
})
複製代碼
輸出結果:
nextTick
promise
複製代碼
理由:
promise.then也是註冊在tick階段的,可是process.nextTick的優先級高於promise,故而先調用process.nextTick
setTimeout(() => {
console.log(1)
}, 0)
new Promise((resolve, reject) => {
console.log(2)
for (let i = 0; i < 10000; i++) {
i === 9999 && resolve()
}
console.log(3)
}).then(() => {
console.log(4)
})
console.log(5)
複製代碼
輸出結果:
2
3
5
4
1
複製代碼
理由:
new promise是個同步操做,故而輸出2和3,而後執行最後一行代碼輸出5。接下來就是promise.then和setTimeout的問題了。咱們知道promise.then和process.nextTick同樣是註冊在tick階段的,而setTimeout是註冊在timer階段的,先進入tick階段執行,而後在進入到下一個輪詢的setTimeout。
setImmediate(() => {
console.log(1)
setTimeout(() => {
console.log(2)
}, 100)
setImmediate(() => {
console.log(3)
})
process.nextTick(() => {
console.log(4)
})
})
setImmediate(() => {
console.log(5)
setTimeout(() => {
console.log(6)
}, 100)
setImmediate(() => {
console.log(7)
})
process.nextTick(() => {
console.log(8)
})
})
複製代碼
輸出結果
1
5
4
8
3
7
2
6
複製代碼
理由:
這裏的tick會合並,因此4和8連續輸出
setImmediate(() => { ---1
console.log(1)
setTimeout(() => { ---2
console.log(2)
}, 100)
setImmediate(() => { ---3
console.log(3)
})
process.nextTick(() => { ---4
console.log(4)
})
})
process.nextTick(() => { ---5
console.log(5)
setTimeout(() => { ---6
console.log(6)
}, 100)
setImmediate(() => { ---7
console.log(7)
})
process.nextTick(() => { ---8
console.log(8)
})
})
console.log(9)
複製代碼
輸出結果
9
5
8
1
7
4
3
6
2
複製代碼
理由: 如圖所示
補充: 1.macrotask:script中代碼、setTimeout、setInterval、I/O、UI render。
2.microtask: promise、Object.observe、MutationObserver,process.nextTick。