缘起
小编最近比较忙,一直没时间更新node event loop,今天终于有时间更新了,此版为小编的封神版,看完这篇,相信各位童鞋一定会更加强大!
node宏认为和微任务
1.node也分宏任务和微任务
2.node宏认为阶段
- timers (定时器:本阶段执行已经被 setTimeout() 和 setInterval() 的调度回调函数)
- pending callbacks(待定回调:执行延迟到下一个循环迭代的 I/O 回调)
- idle, prepare (仅系统内部使用)
- poll (检索新的 I/O 事件;执行与 I/O 相关的回调(几乎所有情况下,除了关闭的回调函数,那些由计时器和 setImmediate() 调度的之外),其余情况 node 将在适当的时候在此阻塞。如:fs.readFile())
- check (setImmediate() 回调函数在这里执行)
- close callbacks (一些关闭的回调函数,如:socket.on(‘close’, …))
3.node微任务阶段
- nextTickQueue
- microTaskQueue
执行顺序和名词解释
node可以宏任务6个阶段,每次清空一个阶段的事件(或者达到上限),就会按照微任务顺序执行微任务。
各个阶段的名词解释如下:
1.timers,这个很简单,就是定时器
2.pending callbacks,这个phase将会执行一些系统的callback操作,比如在做TCP连接的时候,TCP socket接收到了ECONNREFUSED信号,在某些liunx操作系统中将会上报这个错误,那么这个系统的callback将会放到pending callbacks中运行,或者是需要在下一个event loop中执行的I/O callback操作。(不必太关心)
3.idle, prepare,内部的一些事件。(node底层)(用户不必太关心)
4.poll,按照上面的解释就行,这里解释下i/o概念,IO(Input & Output),顾名思义,输入输出即是IO。磁盘,网络,鼠标,键盘等都算IO;而大家通常说的IO,大部分指磁盘和网络的数据操作。
对于磁盘,IO=读写;对于网络,IO=收发。
5.check,按照上面的概念就行,其实就是setImmediate。
6.close callbacks,按照概念即可
7.nextTickQueue,其实就是process.nextTick
8.microTaskQueue,其他微任务,如promise.then
这里重点说明下第四点和第六点
poll轮询
poll将会检测新的I/O事件,并执行与I / O相关的回调,注意这里的回调指的是除了关闭callback,timers,和setImmediate之外的几乎所有的callback事件。
poll主要处理两件事情:轮询I/O,并且计算block的时间,然后处理poll queue中的事件。
如果poll queue非空的话,event loop将会遍历queue中的callback,然后一个一个的同步执行,知道queue消费完毕,或者达到了callback数量的限制。
因为queue中的callback是一个一个同步执行的,所以可能会出现阻塞的情况。
如果poll queue空了,如果代码中调用了setImmediate,那么将会立马跳到下一个check phase,然后执行setImmediate中的callback。
close callbacks
最后一个phase是处理close事件中的callbacks。 比如一个socket突然被关闭,那么将会触发一个close事件,并调用相关的callback。
开发者基本关心
开发者最基本需要关心的是上面的1,4,5,7,8即可,大家按照上面的执行顺序看输出就好了,下面给大家出题了,本次用了node 12.13.0版本
console.log('1');
new Promise(function(resolve) {
console.log('10');
resolve();
}).then(function() {
console.log('11');
});
async function async1() {
console.log('2');
await async2();
console.log('3');
}
async function async2() {
console.log('4');
}
async1();
process.nextTick(function() {
console.log('5');
})
setTimeout(function() {
console.log('6');
process.nextTick(function() {
console.log('7');
})
new Promise(function(resolve) {
console.log('8');
resolve();
}).then(function() {
console.log('9')
})
})
console.log('12');
我是华丽的分隔线
我是华丽的分隔线
我是华丽的分隔线
我是华丽的分隔线
我是华丽的分隔线
我是华丽的分隔线
我是华丽的分隔线
我是华丽的分隔线
我是华丽的分隔线
我是华丽的分隔线
我是华丽的分隔线
我是华丽的分隔线
我是华丽的分隔线
我是华丽的分隔线
我是华丽的分隔线
我是华丽的分隔线
答案公布了
1
10
2
4
12
5
11
3
6
8
7
9
是不是很简单,其实就是在浏览器的基础上加了process.nextTick,接下来来一题难的
console.log('1');
new Promise(function(resolve) {
console.log('10');
resolve();
}).then(function() {
console.log('11');
});
async function async1() {
console.log('2');
await async2();
console.log('3');
}
async function async2() {
console.log('4');
}
async1();
process.nextTick(function() {
console.log('5');
})
setImmediate(function() {
console.log('13');
process.nextTick(function() {
console.log('14');
})
new Promise(function(resolve) {
console.log('15');
resolve();
}).then(function() {
console.log('16')
})
})
setTimeout(function() {
console.log('6');
process.nextTick(function() {
console.log('7');
})
new Promise(function(resolve) {
console.log('8');
resolve();
}).then(function() {
console.log('9')
})
})
console.log('12');
我是华丽的分隔线
我是华丽的分隔线
我是华丽的分隔线
我是华丽的分隔线
我是华丽的分隔线
我是华丽的分隔线
我是华丽的分隔线
我是华丽的分隔线
我是华丽的分隔线
我是华丽的分隔线
我是华丽的分隔线
我是华丽的分隔线
我是华丽的分隔线
我是华丽的分隔线
我是华丽的分隔线
我是华丽的分隔线
答案公布了
1
10
2
4
12
5
11
3
6
8
7
9
13
15
14
16
setImmediate在setTimeout前面,却最后调用,是因为node宏认为先执行的是timers,然后再执行check,如果看不懂,请看下上面的内容。
这里有点要注意,定时器setTimeout和setImmediate也是执行完毕一个,清空微任务,然后执行下一个定时器,和浏览器一致
最后再来看个题,我们加入第四阶段
const fs = require('fs')
fs.readFile('./name.txt','utf8',()=>{
setTimeout(()=>{
console.log('timeout')
})
setImmediate(()=>{
console.log('setImmediate')
});
})
我是华丽的分隔线
我是华丽的分隔线
我是华丽的分隔线
我是华丽的分隔线
我是华丽的分隔线
我是华丽的分隔线
我是华丽的分隔线
我是华丽的分隔线
我是华丽的分隔线
我是华丽的分隔线
我是华丽的分隔线
我是华丽的分隔线
我是华丽的分隔线
我是华丽的分隔线
我是华丽的分隔线
我是华丽的分隔线
答案公布了
setImmediate
timeout
因为我们先在第四阶段(fs),之后到第五阶段(check),然后再回到第一阶段(timers)
总结
1.node中定时器和setImmediate,其实和浏览器一致,都是放入一个执行完毕,再执行下一个,先进先出,不同的是node分阶段,不同的阶段执行顺序会不同
2.node微任务分两步,微任务process.nextTick优先度高
宏认为和微任务扩展
小编看了下资料,其实宏认为和微任务还有好多,下面是不常用的,大家当作扩展看吧
宏任务macrotask:
(事件队列中的每一个事件都是一个macrotask)
优先级:主代码块 > setImmediate > MessageChannel > setTimeout / setInterval大部分浏览器会把DOM事件回调优先处理 因为要提升用户体验 给用户反馈,其次是network IO操作的回调,再然后是UIrender,之后的顺序就难以捉摸了,其实不同浏览器的表现也不太一样,这里不做过多讨论。)
比如:setImmediate指定的回调函数,总是排在setTimeout前面
微任务包括:
优先级:process.nextTick > Promise > MutationObserver
需要多注意 process.nextTick 永远大于 promise.then
尾声
看完这篇,大家是不是搞清楚了node中的事件循环,配合前几期浏览器的事件循环一起看,恭喜童鞋,你的水平提升了!