Node.js 事件循环和浏览器有什么不同?nextTick、Promise、setImmediate 如何排序?

先理解原理和示例,再练习完整口述与追问。

libuv 阶段;nextTick 队列与 Promise 微任务;timer 与 check;说明版本和执行位置,不背全局固定顺序。

浏览器还要安排渲染机会,Node 则主要围绕 I/O、timer、poll、check、close 等事件循环阶段处理回调。process.nextTick 不等于某个普通 libuv 阶段,在适用的回调边界优先处理自己的队列,随后处理相应 Promise 微任务。无限 nextTick 或微任务也可能饿死 I/O。

setImmediate 通常进入 check 阶段,setTimeout 要满足时间门槛。顶层两者先后受运行时条件影响,不能承诺固定;在典型 I/O 回调里同时登记时,setImmediate 通常先于新 timer。Node20 所用 libuv timer 行为有所变化,因此回答输出题应注明 Node 版本、CommonJS/ESM 和登记位置。

js
// Node 16.19,保存为 .cjs,以 CommonJS 顶层执行
console.log('sync')
process.nextTick(() => console.log('tick'))
Promise.resolve().then(() => console.log('promise'))
console.log('end')
// sync end tick promise

先执行同步语句,清空本次 nextTick,再运行 Promise 回调。不要把这个例子不加条件推广到 ESM 顶层或所有嵌套微任务。

setTimeout(fn,0) 不保证立即执行;把 CPU 长循环改成 nextTick 递归也不等于让出主线程。实际慢请求仍要看事件循环延迟与 I/O 耗时。

Node 使用 libuv 组织 I/O 等阶段,nextTick 和 Promise 的处理又有自己的时机。我会先说运行版本和上下文,再区分同步、微任务、timer 和 check;不保证顶层 timer 与 immediate 永远固定顺序,也会警惕高优先级队列无限排入导致 I/O 饥饿。