event loop(Node版本)

缘起


小编最近比较忙,一直没时间更新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中的事件循环,配合前几期浏览器的事件循环一起看,恭喜童鞋,你的水平提升了!

猜你喜欢

转载自blog.csdn.net/zjscy666/article/details/117335725