前言
js 的事件循环机制可能大家都比较熟悉了,分为宏任务和微任务 。
但是 Node.js 的的事件循环机制不知道大家了解多少,这个在面试当中也是比较常问,如果有涉及到 Node.js的 的基本上都会问到事件循环机制,Node.js的事件循环机制和 js 的有所不同,但是大致的理念是相通的,接下来我们就来看一下吧!
核心
Node.js 的事件循环是其异步非阻塞 I/O 模型的核心。它允许 Node.js 在执行非阻塞操作时保持高效,使得单线程的 js 能够处理大量并发操作。
事件循环的基本阶段如下:
- Timers( 定时器 ) :执行
setTimeout()
和setInterval()
设定的回调。 - I/O callbacks( I/O回调 ) :处理几乎所有的 I/O 回调,除了 close 回调、setTimers 和 setImmediate() 的回调。
- Idle, Prepare( 闲置、准备 ) :系统内部使用,处理一些准备工作。
- Poll( 轮询 ) :检索新的 I/O 事件;执行与 I/O 相关的回调(几乎所有的 I/O 回调都在这个阶段处理,除了 close 回调,timers 和 setImmediate() 的回调);节点会在这个阶段检查是否有新的定时器到期,如果有,则回到 timers 阶段执行;如果没有新的 I/O 回调,并且当前也没有定时器到期,则会执行 setImmediate() 的回调。
- Check( 检查 ) :执行
setImmediate()
设定的回调。 - Close Callbacks( 关闭回调 ) :执行关闭事件的回调,如
socket.on('close')
。
在 Node.js 中,宏任务和微任务的概念与浏览器环境类似,但有一些特殊性。
宏任务:
- setTimeout()
- setInterval()
- setImmediate()
- I/O操作(如fs模块的文件操作)
- HTTP请求回调
微任务:
- Promise的then()、catch()和finally()回调
- process.nextTick()
- queueMicrotask()
执行顺序:
-
同步代码
-
微任务
- process.nextTick()队列
- 其他微任务队列(Promise等)
-
宏任务
在每个宏任务执行完毕后,Node.js都会清空微任务队列,然后再进入下一个事件循环阶段。
练习
为了更好的理解,看一段代码
js
const fs = require('fs')
console.log('开始执行')
Promise.resolve().then(() => {
console.log('Promise callback')
})
// 定时器
setTimeout(() => {
console.log('定时器回调1')
}, 0)
setTimeout(() => {
console.log('定时器回调2')
}, 10)
setTimeout(() => {
console.log('定时器回调3')
}, 100)
// I/O操作
fs.readFile('./template.txt', 'utf8', (err, data) => {
if (err) {
console.error('I/O错误:', err)
} else {
console.log('I/O回调:', data)
}
})
// setImmediate
setImmediate(() => {
console.log('setImmediate回调')
})
// process.nextTick
process.nextTick(() => {
console.log('process.nextTick回调')
})
console.log('脚本执行结束')
运行结果:
js
开始执行
脚本执行结束
process.nextTick回调
Promise callback
定时器回调1
setImmediate回调
I/O回调: 我是IO回调
定时器回调2
定时器回调3
解释:
-
宏任务执行:
- "定时器回调1"(setTimeout 0ms)首先执行,这是符合预期的,因为它实际上被设置为1ms延迟。
- "setImmediate回调"在"定时器回调1"之后、"I/O回调"之前执行。这说明在这次运行中,事件循环先进入了检查阶段(执行setImmediate),然后才到达了轮询阶段(执行I/O回调)。
- "I/O回调"在setImmediate之后执行,这是完全可能的,因为I/O操作的完成时间是不确定的。
- "定时器回调2"(setTimeout 10ms)在I/O回调之后执行,这说明10ms的延迟确实比I/O操作和setImmediate的执行时间长。
- "定时器回调3"(setTimeout 100ms)最后执行,这是符合预期的,因为它有最长的延迟时间。
关键点:
setImmediate
和 I/O 回调的顺序: 在这次运行中,setImmediate
回调在 I/O 回调之前执行。这展示了Node.js 事件循环的一个重要特性:检查阶段(执行 setImmediate )可能会在某些 I/O 操作完成之前到来。- 定时器的行为: 0ms、10ms 和 100ms 的定时器确实按照预期的顺序执行,但它们的实际执行时间可能会受到其他操作(如I/O和setImmediate)的影响。
- I/O操作的不确定性: I/O回调的执行时机证明了异步I/O操作的完成时间是不可预测的,它可能在setImmediate之后,但在较长延迟的定时器之前完成。