浏览器中 JS的事件循环相信大家都很熟悉。nodejs 是和操作系统打交道, 所以学习起来会有一点难度。
文章导航
一、node事件循环图
这里面会涉及到一些多线程, 本文只讨论跟 JS 线程有关的信息。
二、执行过程
代码的运行首先会经过正常的执行, 当**同步代码**执行完毕之后就会去看还有没有其余线程的信息, 比如 setTimeout
这里就进入上图中的 event loop 右边的循环执行。
每一次循环会经过 timers >> pending callback
>> idle prepare
>> poll >> check >> close callbacks
这六个阶段(一次循环)
这六个都是队列
这里主要讨论 timers、poll、check 队列
timers (计时器队列)
- timers 队列主要负责处理计时器的回调函数
poll (轮询队列)
- poll 存放除了 timers、check 绝大部分回调都会放在该队列,比如:读写文件内容的回调函数,监听用户的请求的回调函数。
import { readFile } from 'fs';
readFile('/etc/passwd', (err, data) => { // 该回调函数进入的是 poll 队列
if (err) throw err;
console.log(data);
});
- poll队列的运作方式
1、如果 **poll**中有回调,依次执行回调,直到清空队列。
2、如果 **poll**中没有回调
- 等待其它队列中出现回调,结束该阶段,进入下一阶段
- 如果其它队列也没有回调,持续等待,知道出现回调为止
check (检查阶段)
使用 setImmediate
的回调会直接进入这个队列。
setImmediate
函数可以当做是: setTimeout(callback,0)
setImmediate(() => {
console.log(123)
})
setTimeout(() => {
console.log(345)
}, 0)
// 输出的顺序 有个能345 在前也有可能 123
// 因为 setTimeout 不存在取到 0 只能 >0, 所以当进入 timers 队列时不一定存在 回调函数。
setImmediate
所以它一定是异步的, 但是异步回调函数进入的是 check 队列。
现在大致了介绍 timer、poll、check 队列,下面用一段代码解释执行的顺序。
const fs = require('fs')
fs.readFile('./index.js', 'utf-8', function test () {
console.log('读取文件的回调函数')
setTimeout(() => {
console.log(1)
}, 0)
setImmediate(() => {
console.log(2)
})
})
这段代码的执行结果为
读取文件的回调函数
2
1
使用事件循环的角度来分析为什么是这个输出结果:
代码执行到读取文件, 当文件读取完时会触发 test
回调函数, test
回调函数加入 poll 队列, **poll**队列执行 test
回调函数, 输出 读取文件的回调函数
, 然后执行到了 setTimeout
那么会通知**计时器**线程监听(当时间到达时, 才会将setTimeout
的回调函数加入 timers 队列),继续执行到 setImmediate
直接将回调函数加入**check**队列, 然后test
执行完毕,此时**poll**队列执行完毕, 然后监听其它队列是否有需要执行的信息, 发现**check**队列需要执行,然后就到了**check**队列执行输出2
,**check**也执行完毕然, 然后此时 计时器 往 timers 加入了回调函数(加入的时间点并不能确定), 所以当 check 执行完毕时又进入了行的事件循环, 此时 timers 有需要执行的信息, 然后输出 1
, 当 timers 执行完毕,系统发现没有什么需要执行的就结束了程序执行。
解释图中的 NextTick、Promise
NextTick、Promise 也是队列,但是它们不同,上面的事件队列可以说是 **宏任务**, 它们两则是 微任务 它们并不存在事件循环中, 它们两有一个优先级:先执行 NextTick 然后在执行 Promise
NextTick、Promise 怎么配合事件循环的
用一句话说就是:事件循环中,每次打算执行一个 回调函数 必须先清空 NextTick、Promise 列表
还是使用一段代码解释:
process.nextTick(() => {
console.log(1)
process.nextTick(() => {
console.log(2)
})
})
console.log(3)
Promise.resolve().then(() => {
console.log(4)
process.nextTick(() => {
console.log(5)
})
})
setTimeout(() => {
console.log(6)
})
输出的结果为 3 1 2 4 5 6
- 先输出同步代码 3
- 进入 NextTick 输出 1, 然后又往 NextTick 加入回调函数, 由于优先级问题马上执行 NextTick 输出2
- 当 NextTick 队列清空后, 在执行 Promise 队列输出 4,然后又往 NextTick 队列加入回调函数, 当 Promise 执行完毕时,此时 NextTick 有信息马上执行 输出 5
- 此时 NextTick、Promise 队列已经清空了然后进入事件循环 timers 输出 6
三、总结
如果想要最快的执行异步回调函数,那么可以使用将回调函数加入 NextTikc 队列的api, 其次是 **Promise**, nodejs 的事件循环了解之后对我们的日常工作起到一定的好处。
延展阅读:
CAP理论与Raft协议如何在分布式系统中确保一致性和可用性?
为什么PHP在近年来逐渐失去了领先地位,而GoLang却迅速崛起?
咨询方案 获取更多方案详情