setImmediate 的底层实现完全依赖 libuv 的 check 阶段,是注册到事件循环的原生异步机制;它在 poll 阶段空闲后执行,每轮最多一次,不参与定时器排序,优先级低于 process.nextTick。

setImmediate 的底层实现完全依赖 libuv 的 check 阶段,它不是 JavaScript 层的“模拟”或“降级方案”,而是直接注册到 libuv 事件循环中的原生异步机制。
check 阶段是 setImmediate 的执行归属地
libuv 的事件循环严格分为六个阶段,其中 check 阶段专用于执行 setImmediate 注册的回调。该阶段在 poll 阶段之后、close callbacks 阶段之前被调用。当 Node.js 完成 I/O 轮询(poll)并确认没有待处理的 I/O 事件时,就会进入 check 阶段,一次性清空当前所有已排队的 setImmediate 回调。
- 每个事件循环周期最多执行一次 check 阶段,因此所有 setImmediate 回调都在同一批次中运行
- 它不参与定时器排序,也不受最小延迟限制(不像 setTimeout(…, 0) 至少延迟 1ms)
- 若 poll 阶段有活跃 I/O(如网络连接持续收包),check 阶段可能被推迟,直到 poll 空闲
libuv 如何注册和触发 setImmediate 回调
Node.js 在调用 setImmediate 时,会通过 libuv 的 C API 向当前 loop 的 check 队列插入一个 uv_check_t 类型的 handle。这个 handle 是轻量级结构体,内部绑定用户传入的 JavaScript 回调包装器。
- uv_check_init 初始化 handle,并将其挂入 loop->check_handles 链表
- uv_check_start 启用该 handle,使其在每次 check 阶段被遍历执行
- libuv 在 uv_run 中按顺序调用 uv__run_check(loop),逐个触发链表中所有 active 的 check handle
与 process.nextTick 的关键区别:不在同一调度层级
process.nextTick 不经过 libuv 任何阶段,它维护的是 V8 引擎侧的独立微任务队列,在每个操作(包括同步代码、I/O 回调、setImmediate 执行完)结束后立即清空;而 setImmediate 必须等待整个事件循环推进到 check 阶段才能执行。
- nextTick 回调总在当前操作结束、下一轮循环开始前执行,优先级高于 setImmediate
- 即使在 I/O 回调里调用 nextTick 和 setImmediate,前者也一定先于后者输出
- setImmediate 可通过 ref()/unref() 控制是否让事件循环因它而保持活跃,nextTick 则无此能力
实际行为受 I/O 活动影响明显
setImmediate 的执行时机并非固定“下一刻”,而是取决于 poll 阶段是否阻塞。例如:
- 无 I/O 时(如纯计算脚本),setImmediate 通常紧随 setTimeout(..., 0) 之后执行,但顺序不保证
- 有活跃 I/O(如 fs.readFile 后立即 setImmediate),它一定在 I/O 回调之后、且 poll 空闲后才执行
- 若 poll 阶段持续有新事件(如长连接不断收包),check 阶段可能被跳过若干轮,导致 setImmediate 延迟


















