大多数人对浏览器的事件循环已经非常熟悉了,但是对node的事件循环就没那么清晰了。今天觉得很有必要,写一篇文章用言简意赅的方式把node事件循环这个问题说清楚。
node事件循环一共有3个队列。
- Timer队列:用于处理setTimeout、setInterval等计时器。
- Poll队列:用于处理文件的读写、数据库操作、网络请求等操作等。
- Check队列:用于处理setImmediate的回调。
事件循环初始化完成之后,会自上而下循环开始执行。先查看Timer队列是否有任务,有的话就立即执行,否则进入到Poll队列。查看Poll队列是否有任务,有任务就立即执行,否则就停在Poll队列进行等待。查看Check队列和Timer队列是否有任务,有的话就跳转到对应的队列中执行,然后回到Poll队列中继续等待。
大概原理知道了后,用几个例子来巩固一下这个知识点。
案例1
const fs = require('fs');
console.log('开始执行');
setTimeout(() => {
console.log('我是定时器1');
}, 20)
// 假设读取的时间也是花费了20ms时间
fs.readFile('./readme.md', 'utf-8', (err, data) => {
console.log('开始读取数据')
})
console.log('结束执行');
看一下这段代码输出的顺序是什么?
开始执行 => 结束执行=> 开始读取数据 => 我是定时器1 。
先开始执行主程序的代码:开始执行和结束执行。并且将setTimeout和fs.readFile都推入异步任务模块中对事件循环进行初始化。进入到timer队列,为空;进入到Poll队列为空,继续阻塞等待;20ms后把setTimeout插入到Timer队列中,把fs.readFile插入到Poll队列中。此时js引擎正停在Poll队列中执行fs.readFile的回调,输出开始读取数据,执行完成之后跳到Timer队列,输出我是定时器1。
好,我们再继续复杂一下这个案例。
案例2
const fs = require('fs');
console.log('开始执行');
setImmediate(() => {
console.log('我是setImmediate');
})
// 假设读取的时间也是花费了20ms时间
fs.readFile('./readme.md', 'utf-8', (err, data) => {
console.log('开始读取数据')
})
process.nextTick(() => {
console.log('我是nextTick');
})
console.log('结束执行');
看一下这段代码输出的顺序是什么?
开始执行 => 结束执行 => 我是nextTick => 我是setImmediate => 开始读取数据
此时我们看到了我是nextTick他的执行时机比较快,那么它又在事件循环的哪一个步骤中执行的呢?通过查阅资料,我们可以看到它是这样的。
- process.nextTick:属于异步模块里面,但不属于事件循环的一部分,会在事件循环之前执行。
搞了这么久还没看到promise的存在呢,那第三个例子我将引入Promise进来,对比一下Promise这个微任务会在整个队列中哪里执行。
案例3
const fs = require('fs');
console.log('开始执行');
setImmediate(() => {
console.log('我是setImmediate');
})
process.nextTick(() => {
console.log('我是nextTick');
})
Promise.resolve().then(() => {
console.log('我是promise');
})
console.log('结束执行');
看一下这段代码输出的顺序是什么?
开始执行 => 结束执行 => 我是nextTick => 我是promise => 我是setImmediate
是不是有一点点迷惑,为什么promise是在这个位置执行?
- new Promise().then(回调):跟
process.nextTick一样不属于事件循环,而是属于异步模块的任务,它的执行时间在nextTick之后,在事件循环之前。
总结
下面用一张图来展示整个node的事件循环执行过程,他来自前端星球的一个视频。
最后,我们把上面的几个部分做一下总结。
异步模块的执行
- process.nextTick:属于异步模块里面最新执行的任务。
- new Promise().then(回调):执行时间在nextTick之后,在事件循环之前。
node事件循环一共有3个队列。
- Timer队列:用于处理setTimeout、setInterval等计时器。
- Poll队列:用于处理文件的读写、数据库操作、网络请求等操作等。
- Check队列:用于处理setImmediate的回调。
如果这篇文档对你有帮助,欢迎点赞、关注或者在评论区留言,我会第一时间对你的认可进行回应。精彩内容在后面,防止跑丢,友友们可以先关注我,每一篇文章都能及时通知不会遗失。