You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node.js事件循环在阻塞代码下的行为差异及微任务优先级疑问

Node.js事件循环在阻塞代码下的行为差异及微任务优先级疑问

嘿,这个问题问得特别到位,正好戳中了Node.js事件循环里容易被忽略的「阶段顺序+异步任务调度时机」细节!我来给你一步步拆解清楚~

先还原你的问题场景

你的测试代码

const fs = require('node:fs/promises');

// Schedule a timeout
setTimeout(() => {
  console.log("set timeout callback is called!");
}, 10);

function consumeCpu(limit) {
  console.log(`waiting a while (limit is ${limit})`);
  let sum = 1;
  for (let i = 0; i < limit; i++) {
    sum += i;
  }
}

let cycles = Number(process.argv[2]);

// Wait a while (blocking CPU)
consumeCpu(cycles);

// Run an async I/O action which will fail
(async () => {
  try {
    // This file does not exist, so an error will be thrown
    let data = await fs.readFile('/tmp/123.txt');
  } catch (err) {
    console.log("err is: " + err);
  }
})();

// Wait a while again (blocking CPU)
consumeCpu(cycles);
console.log("end of script");
// Let event loop start processing

两种运行结果

短阻塞(node test.js 100)输出:

waiting a while (limit is 100)
waiting a while (limit is 100)
end of script
err is: Error: ENOENT: no such file or directory, open '/tmp/123.txt'
set timeout callback is called!

长阻塞(node test.js 1000000000)输出:

waiting a while (limit is 1000000000)
waiting a while (limit is 1000000000)
end of script
set timeout callback is called!
err is: Error: ENOENT: no such file or directory, open '/tmp/123.txt'

核心原因:事件循环阶段顺序 + 异步任务的调度时机

你之前的认知「微任务优先于宏任务」没错,但这里的关键是:微任务的创建本身依赖于前面的宏任务(I/O回调)被执行,而长阻塞场景下,定时器的宏任务已经先进入了事件循环的优先处理阶段。

我给你拆解两种场景的执行流程:

1. 短阻塞场景(阻塞时间<10ms)

  • 同步代码先跑完:两次consumeCpu(100) → console.log("end of script")。此时setTimeout的10ms超时时间还没到,定时器回调还没被加入事件循环的timers阶段队列。
  • 事件循环启动,首先进入timers阶段:没有可执行的回调,直接跳过。
  • 进入poll阶段(处理I/O回调的核心阶段):此时fs.readFile的I/O操作已经完成(因为操作系统后台处理I/O和Node.js的CPU阻塞是并行的),它的错误回调被执行,触发了promise的reject,进而把catch里的代码加入微任务队列。
  • 处理微任务队列:执行catch的console.log,输出err is: ...。
  • 事件循环继续循环,再次进入timers阶段:此时setTimeout已经超时,执行它的回调,输出set timeout callback is called!。

2. 长阻塞场景(阻塞时间>>10ms)

  • 同步代码执行期间:因为阻塞时间远超过10ms,setTimeout的超时时间已到,它的回调被加入事件循环的timers阶段队列。同时,fs.readFile的I/O操作也已经完成,它的错误回调被加入poll阶段队列。
  • 同步代码跑完后,事件循环启动,首先进入timers阶段:找到已经超时的setTimeout回调,直接执行,输出set timeout callback is called!。此时微任务队列是空的(因为fs.readFile的回调还没被执行,promise还没被reject,自然没有相关微任务)。
  • 事件循环继续推进,依次跳过pending callbacks、idle/prepare阶段,进入poll阶段:执行fs.readFile的错误回调,触发promise reject,把catch代码加入微任务队列。
  • 处理微任务队列:执行catch的console.log,输出err is: ...。

总结

你的误区是默认「promise的微任务已经存在」,但实际上fs.promises.readFile对应的微任务(catch里的逻辑),必须等Node.js的事件循环处理完I/O回调、触发promise reject后才会被创建。而长阻塞场景下,定时器的宏任务已经先进入了事件循环的第一个处理阶段(timers),自然会先被执行。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 07:17:58