浏览器的事件循环比较熟悉了,也来了解下 node 的。
参考来源:
https://nodejs.org/en/guides/event-loop-timers-and-nexttick/
https://juejin.cn/post/6844903999506923528
事件循环分为 6 个阶段,图中每个框都是一个阶段,每个阶段都有一个先进先出的队列来存储要执行的回调。
每当进入一个阶段,会执行最大数量为 n 个的回调,或者队列回调数少于 n,执行完后进入下一个阶段。
- timers,执行setTimeout、setInterval 的回调。设定一个下限时间(如果写 0,会被改为 1),之后会执行这些回调,这里可能会被 poll 延迟
- pending callbacks,执行 操作系统相关的回调,比如 TCP 连接还没连接上就收到了数据,一些操作系统会报错,这就会在这个队列加ECONNREFUSED事件
- idle, prepare,node内部使用
- poll,轮询,做两件事
- 计算因为 I/O要阻塞和轮询多久(计算出来然后呢?)
- 处理轮询队列里的事件
- 如果到了poll 阶段
- 如果轮询队列不为空,会同步执行最大数量为 n 的回调
- 如果轮询队列为空,
- 如果有setImmediate 会进入 check 阶段执行setImmediate的回调
- 如果 timers到了时间(setTimeout、setInterval 那些),回到 timers 阶段
- check,setImmediate回调在这里执行
- close callbacks,一些关闭回调,比如 socket.on(‘close’, …)
了解完事件循环各个阶段,有什么用?可能能避免踩一些坑?虽然之前不了解这个机制,写 node 也没踩过坑🤦♂️,万一以后会遇到呢?
setTimeout(()=>{
console.log('timeout');
},0);
setImmediate(()=> {
console.log('immediate');
});
这里似乎直觉上会先出 timeout,但实际 setTimeout 的时间最小是 1。如果机器性能一般,到 timers 阶段已经够 1ms 了,那就先输出 timeout;否则先输出 immediate
再来看这个
var fs = require('fs')
fs.readFile(__filename, () => {
setTimeout(() => {
console.log('timeout');
}, 0);
setImmediate(() => {
console.log('immediate');
});
});
这里是先 immediate 再 timeout,因为是在回调里执行的,就是在 poll 阶段,当队列为空,immediate 优先级更高。
Promise 和process.nextTick 在一个事件循环后执行
再看看这题,看你晕不晕
async function async1(){
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2(){
console.log('async2')
}
console.log('script start')
setTimeout(function(){
console.log('setTimeout0')
},0)
setTimeout(function(){
console.log('setTimeout3')
},3)
setImmediate(() => console.log('setImmediate'));
process.nextTick(() => console.log('nextTick'));
async1();
new Promise(function(resolve){
console.log('promise1')
resolve();
console.log('promise2')
}).then(function(){
console.log('promise3')
})
console.log('script end')
script start
async1 start
async2
promise1
promise2
script end
nextTick
async1 end
promise3
setTimeout0
setImmediate
setTimeout3
为了方便对照,贴个图片左右对比
Promise 里的是立即执行的,resolve 和 reject 回调会加到微任务,所以 async1 start 后就会输出 async2。
也因此之后会走到 promise1,promise1 后,把 resolve 回调继续加到微任务,接着就输出 promise2。
之后就走到同步代码的最后 script end。
然后发现微任务队列不为空,最早的就是 nextTick,之后就是 await async2 后面的 async1 end 了。(这里可能疑问为啥都加了 await 了,没有同步执行,因为 async1()前面没加 await)
然后微任务队列还不为空,还有promise3。
之后进入事件循环,这里根据性能影响,最后三个可能会有区别,可能setTimeout3 还在 setImmediate 前面,也可能 setImmediate 在最前面。
好了搞清楚这些有啥用呢?可能是在之后遇到灵异事件时能较快找到原因吧? 顺便满足下好奇心,哈哈。