要快速区分一段代码运行在 Node.js 还是浏览器环境中,有一个几乎百试百灵的“金标准”:观察它能否使用 setImmediate。这个 API 是 Node.js 事件循环模型里一个极具标志性的特征,浏览器中根本不存在。

setImmediate 只存在于 Node.js 的事件循环中
这个 API 的“出身”十分明确:它属于 Node.js 事件循环的 check 阶段。这一阶段的位置非常关键,紧跟在负责处理 I/O 事件的 poll 阶段之后,位于 close callbacks 阶段之前。整套多阶段循环机制是 Node.js 为高效处理异步 I/O 而专门设计的,浏览器的事件循环模型根本没有“check 阶段”这一概念,自然也不存在原生的 setImmediate。
- 在 Node.js 中,编写
setImmediate(() => console.log('hi')),该回调一定会进入 check 阶段的队列。 - 同样的代码放入浏览器控制台,只能得到一个无情的报错:
ReferenceError: setImmediate is not defined。 - 当然,社区提供了
setimmediate这样的 polyfill 包来模拟其行为,但那只是“形似”,底层的调度机制与原生事件循环阶段完全不同。
执行时机对比:I/O 后的“立即” vs 浏览器的“宏任务尾部”
setImmediate 在 Node.js 中有一个经典特性:在 I/O 回调(例如一个 fs.readFile 完成)内部注册时,它总是比 setTimeout(fn, 0) 先执行。原因在于,I/O 回调本身在 poll 阶段执行,执行完毕后事件循环会立刻进入 check 阶段,从而清空 setImmediate 队列。而 setTimeout 即使延迟设为 0,其回调也要等到下一个 timers 阶段。
反观浏览器,事件循环没有这种与 I/O 强绑定的阶段划分。setTimeout、setInterval、UI 渲染等都作为宏任务(macrotask)排队,Promise.then、MutationObserver 等则属于微任务(microtask)。setTimeout(fn, 0) 与微任务的执行顺序,完全由宏/微任务的调度规则决定,找不到像 check 阶段这样固定的“插队点”。
- 因此,在 Node.js 的 I/O 回调中同时写入
setImmediate和setTimeout(fn, 0),输出顺序稳定可预测:前者先执行,后者后执行。 - 在浏览器中,你无法复现这一特性,因为底层模型完全不同。
与 process.nextTick 的层级关系暴露运行时本质
另一个强有力的佐证是观察 setImmediate 与 process.nextTick 的“同框”。process.nextTick 在 Node.js 中是一个更特殊的存在,它不属于任何事件循环阶段,而是在当前操作结束后、进入下一个事件循环阶段 之前,以最高优先级被立即执行。你可以把它理解成一个“VIP 插队通道”。
而 setImmediate,顾名思义,是“立即”执行,但这个“立即”是相对于事件循环阶段而言的——它老老实实排队在 check 阶段。这种“插队者(nextTick)vs 阶段内正规军(setImmediate)”的明确分工与优先级差异,只有在 Node.js 这种多阶段事件循环模型里才有讨论的意义。
- 浏览器的事件循环抽象层级更简单,只有宏任务和微任务两层,既没有
process.nextTick,也没有 check 阶段。 - 因此,当你看到代码中讨论或比较
process.nextTick与setImmediate的执行顺序时,几乎可以百分百断定,这段代码的上下文是 Node.js 环境。
