浅谈 nodejs 的事件循环机制

前言

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 中,宏任务和微任务的概念与浏览器环境类似,但有一些特殊性。

宏任务:

  1. setTimeout()
  2. setInterval()
  3. setImmediate()
  4. I/O操作(如fs模块的文件操作)
  5. HTTP请求回调

微任务:

  1. Promise的then()、catch()和finally()回调
  2. process.nextTick()
  3. queueMicrotask()

执行顺序:

  1. 同步代码

  2. 微任务

    • process.nextTick()队列
    • 其他微任务队列(Promise等)
  3. 宏任务

在每个宏任务执行完毕后,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. 宏任务执行:

    • "定时器回调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)最后执行,这是符合预期的,因为它有最长的延迟时间。

关键点:

  1. setImmediate和 I/O 回调的顺序: 在这次运行中,setImmediate回调在 I/O 回调之前执行。这展示了Node.js 事件循环的一个重要特性:检查阶段(执行 setImmediate )可能会在某些 I/O 操作完成之前到来。
  2. 定时器的行为: 0ms、10ms 和 100ms 的定时器确实按照预期的顺序执行,但它们的实际执行时间可能会受到其他操作(如I/O和setImmediate)的影响。
  3. I/O操作的不确定性: I/O回调的执行时机证明了异步I/O操作的完成时间是不可预测的,它可能在setImmediate之后,但在较长延迟的定时器之前完成。
相关推荐
ᥬ 小月亮几秒前
Ant design vue中的日期
前端·javascript·vue.js
拄杖盲学轻声码11 分钟前
【html网页制作】国庆节日主题网页制作含js轮播(5页面附效果源码)
前端·javascript·html
江凡心18 分钟前
Qt 每日面试题 -5
服务器·数据库·qt·学习·面试
you来有去23 分钟前
npm install报错npm ERR! Found: vite@4.5.0
前端·npm·node.js
等什么君!44 分钟前
初识Vue3(详细版)
前端·vue
程序员奇奥1 小时前
Vue中对数组变化监听
前端·javascript·vue.js
胡西风_foxww1 小时前
用css画一个loading
前端·css·loading·加载中
i80131 小时前
弹性盒模型关键几个点:
前端·javascript·css
时清云1 小时前
【算法】搜索二维矩阵
前端·算法·面试
挣钱花3881 小时前
mapboxGL 离线部署或者说去除token最简单得方法
开发语言·前端·javascript