CommonJS/ESM 中的 process.nextTick 与从 queueMicrotask 执行顺序是什么?

一、why?

在 Node.js 中 promise.then 的优先级往往低于 process.nextTick 的优先级,但是也不是绝对的。 在 ESM 中测试时候,问题发生了反转。

二、前置知识:Node.js 脚本执行的各个阶段

阶段 描述
启动阶段 (Start) - 执行全局模块的代码。
- 初始化全局变量和函数。
事件循环阶段 timer 阶段: 执行 setTimeout 或 setInterval 注册的回调函数。
pending callback 阶段: 执行上一轮事件循环遗留的被延时的 I/O 回调函数。
idle prepare 阶段: 仅用于 Node.js 内部模块的使用。
poll 轮询阶段: 执行异步 I/O 的回调函数;计算当前轮询阶段阻塞后续阶段的时间。
check 阶段: 当 poll 阶段回调函数队列为空时,执行 setImmediate 回调函数。
close 阶段: 执行注册 close 事件的回调函数。
执行微任务 (Microtasks) - 处理 process.nextTick 注册的回调函数。
- 处理 Promise 的回调函数。
清理阶段 (Cleanup) - 执行一些清理工作,例如关闭文件描述符等。
退出阶段 (Exit) - 执行 process.on('exit') 注册的回调函数。

三、执行脚本

从事件循环中,围绕其周围的任务,都会与微任务队列进行交互 nextTickQueue 和 Promise Queue 和 microstack 进行交互。

四、问题模拟

定义两个文件:

  • CommonJS: index.cjs
  • ESM: index.mjs

内容都是一样的:

ts 复制代码
process.nextTick(() => console.log(1));
Promise.resolve().then(() => console.log(2));
  • 执行 index.cjs 输出的顺序是: 1 2
  • 执行 index.mjs 输出的顺序是: 2 1

五、为什么?

这是因为,当作为 ESM 运行时,脚本实际上已经处于微任务阶段。因此,从那里排队的新微任务将在""回调之前执行nextTick。

esm 使用 async/await 评估

在 Node 中,ESM 模块脚本实际上是通过 async/await 函数进行评估的:

ts 复制代码
async run() {
    // ...
    try {
      await this.module.evaluate(timeout, breakOnSigint);
    } catch (e) {
      // ...
    }
}

因此,这意味着当我们的脚本运行时,我们已经进入微任务阶段,因此从那里排队的新微任务将首先执行。

cjs 在 poll 阶段

在 commonjs 代码中,依然是同步运行,代码运行处在事件循环的轮询 (poll)阶段

但当作为 CommonJS 运行时,仍处于轮询阶段。入口点就在这里,然后一切直到实际评估都是同步的。

ts 复制代码
setImmediate(() => {
  let winner;
  console.log("轮询的结果:");
  queueMicrotask(() => {
    if (!winner) console.log(winner = "microtask");
  });
  process.nextTick(() => {
    if (!winner) console.log(winner = "nextTick");
  });
});
// 轮询的结果:
// nextTick

在 setImmediate 中进行,处于检查阶段,在里面创建两个任务 queueMicrotask 和 process.nextTick, 输出的结果是 nextTick, 说明 process.nextTick 的优先级高。

六、结论

本文关注 esm 下 nextTick 与 promise.then 执行顺序的差异行为。在 ESM 中由于在 async/await 的上下文中 已经处于微任务的环境中 promise.then 直接加入微任务队列中,比 process.nextTick 更快执行。

相关推荐
Jmyd01234 小时前
Web端3D营销展厅技术实现路径:从素材上云到线索回流的数据链路
前端·3d·性能优化·三维数字化
砚凝霜4 小时前
【软考信息安全】第二十二章 Web服务器安全分析与增强——Apache与IIS
服务器·前端·安全
大鸡腿同学5 小时前
手心向上,手心向下
后端
濮水大叔5 小时前
NestJS Dynamic Providers 与 Cabloy/Vona Provider Scene:同接口多实现的两种装配哲学
typescript·node.js·nestjs
CCblandy6 小时前
令牌桶在 0.9 倍负载超发 19%、固定窗口边界放行 20 个:四种限流算法同构实测
后端·python·算法·性能优化·架构
u0111026756 小时前
JPG、PNG、WebP 怎么选?按透明度、体积和使用场景做判断
前端·javascript·vue.js·图像处理·ai作画
jerrywus7 小时前
让 AI 写完代码后交一份报告:我改造了 answer-me-with-html
前端·claude·cursor
派小心.7 小时前
小程序分享链路中来源参数的传递校验
大数据·前端·数据库·人工智能·小程序·数据分析
凤山老林7 小时前
Spring Boot 集成 Sa-Token 实现高可用权限认证:登录鉴权、动态路由权限与接口防重放实战
spring boot·后端·sa-token·bootstrap
hhb_6187 小时前
一键生成数据看板,ECharts实战演示
前端·javascript·echarts