在瀏覽器事件循環(huán)中,我們了解到javascript
在瀏覽器中的事件循環(huán)機(jī)制,其是根據(jù)html5
定義的規(guī)范來實(shí)現(xiàn)的
而在nodejs
中,事件循環(huán)是基于libuv
實(shí)現(xiàn),libuv
是一個多平臺的專注于異步IO
的庫,如下圖
image.png
上圖
EVENT_QUEUE
給人看起來只有一個隊列,但是EventLoop
存在6個階段,每個階段都有對應(yīng)的一個先進(jìn)先出的回調(diào)隊列
流程
image.png
- timers階段:這個階段執(zhí)行
timer(setTimeout,setInterval)
的回調(diào) - 定時器檢測階段(timers):本階段執(zhí)行
timer
的回調(diào),即setTimeout,setInterval
里面的回調(diào)函數(shù) - I/O 事件回調(diào)階段:執(zhí)行延遲到下一個循環(huán)迭代的
I/O
回調(diào),即上一輪循環(huán)中未被執(zhí)行的一些I/O
回調(diào) - 限制階段:僅供系統(tǒng)內(nèi)部使用
- 輪訓(xùn)階段poll:檢索新的
I/O
事件,執(zhí)行與I/O
相關(guān)的回調(diào),幾乎所有的情況下,除了關(guān)于的回調(diào)函數(shù),那些計時器和setImmediate()
調(diào)度之外,其余情況node
將在適當(dāng)?shù)臅r候在此阻塞 - 檢查階段:
setImmediate()
回調(diào)函數(shù)在這里執(zhí)行 - 關(guān)閉事件回調(diào)階段:一些關(guān)閉的回調(diào)函數(shù),如:
socket.on('close',……)
每個階段對應(yīng)一個隊列,當(dāng)事件循環(huán)進(jìn)入某個階段時,將會在該階段內(nèi)執(zhí)行回調(diào),直到隊列好近或者回調(diào)的最大數(shù)量已執(zhí)行,那么將進(jìn)入下一個階段
除了上述6個階段,還存在process.nextTick
,其部署事件循環(huán)的任何一個階段,屬于該階段與下階段之間的過度,即本階段執(zhí)行結(jié)束,進(jìn)入下一個階段前,所要執(zhí)行的回調(diào),類似插隊
image.png
在Nodejs中,同樣存在宏任務(wù)和微任務(wù),與瀏覽器中的時間循環(huán)相似
微任務(wù)對應(yīng)的有:
- next tick queue:process.nextTick
- other queue: Promise的then回調(diào),queueMicrotask
宏任務(wù)對應(yīng)的有:
- timer queue:setTimeout,setInterval
- poll queue: IO事件
- check queue: setImmediate
- close queue:close事件
其執(zhí)行順序為:
- next tick microtask queue
- other microtask queue
- timer queue
- poll queue
- check queue
- close queue
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2(){
console.log("async2")
}
console.log("script start")
setTimeout(()=>{
console.log("setTimout0")
},0)
setTimeout(function () {
console.log('setTimeout2')
}, 300)
setImmediate(() => console.log('setImmediate'));
process.nextTick(() => console.log('nextTick1'));
async1();
process.nextTick(() => console.log('nextTick2'));
new Promise(function (resolve) {
console.log('promise1')
resolve();
console.log('promise2')
}).then(function () {
console.log('promise3')
})
console.log('script end')
分析過程:
- 先找到同步任務(wù),輸出script start
- 遇到第一個setTimout將里面的函數(shù)放入timer隊列中
- 遇到第二個setTimout,300ms后將里面的回調(diào)函數(shù)放到timer隊列中
- 遇到第一個setImmediate,將里面的回調(diào)函數(shù)放到check隊列中
- 遇到第一個nextTick,將里面的回調(diào)函數(shù)放到本輪同步任務(wù)執(zhí)行完畢后執(zhí)行
- 執(zhí)行async1 函數(shù),輸出async1 start
- 執(zhí)行async2 函數(shù),輸出async2 ,async2后面輸出async 1 end進(jìn)入微任務(wù),等待下一輪事件循環(huán)
- 遇到第二個,將里面的回調(diào)函數(shù)放到本輪同步任務(wù)執(zhí)行完畢后執(zhí)行
- 遇到new Promise,執(zhí)行里面的立即執(zhí)行函數(shù),出書promise1,promise2
- then里面的回調(diào)函數(shù)進(jìn)入到微任務(wù)隊列
- 遇到同步任務(wù),輸出script end
- 執(zhí)行下一輪回調(diào)函數(shù),先依次輸出nextTick函數(shù),分別是nextTick1,nextTick2
- 然后執(zhí)行微任務(wù)隊列,依次輸出async1 end,promise3
- 執(zhí)行timer隊列,依次輸出setTimeout0
- 接著執(zhí)行check隊列,依次輸出setImmedate
- 300ms后,timer隊列存在任務(wù),執(zhí)行輸出setTimeout2
script start
async1 start
async2
promise1
promise2
script end
nextTick1
nextTick2
async1 end
promise3
setTimeout0
setImmediate
setTimeout2