原生Promise与Bluebird Promise差异及事件循环执行顺序疑问
这问题问到点子上了,我当初刚接触Node.js事件循环时,也被原生Promise和Bluebird的执行顺序差异搞懵过,咱们一步步拆解清楚。
先搞懂原生代码的执行逻辑
先看你给出的原生代码执行顺序:Starting... ... → process.nextTick() → Promise → setImmediate,这背后是Node.js事件循环的微任务优先级规则:
Node.js里有两类优先级不同的微任务队列:
nextTickQueue:专门存放process.nextTick()的回调,是所有异步任务里优先级最高的——每完成一个事件循环阶段,都会先清空这个队列,再处理其他任务。Microtask Queue:存放原生Promise的then/catch/finally、queueMicrotask()的回调,优先级低于nextTickQueue,必须等nextTickQueue清空后才会被处理。
而setImmediate属于宏任务,会在事件循环的check阶段执行,要等所有微任务处理完才轮到它。
对应你的代码执行流程:
- 同步代码先跑,直接输出
Starting... ... - 依次注册三个异步任务:
setImmediate回调进入宏任务的check阶段队列- 原生Promise的
then回调进入Microtask Queue process.nextTick()回调进入nextTickQueue
- 同步代码执行完,先处理最高优先级的
nextTickQueue,输出process.nextTick() - 接着清空
Microtask Queue,执行Promise回调输出Promise - 所有微任务处理完毕,进入事件循环的
check阶段,执行setImmediate输出对应内容
Bluebird带来的差异及原因
Bluebird并没有依赖Node.js原生的Microtask Queue来调度Promise回调,而是自己实现了一套异步调度机制,这是核心差异点:
在Node.js环境下,Bluebird默认会把Promise的then回调直接添加到nextTickQueue里(和process.nextTick()共用同一个队列)。这就改变了任务的排队顺序:
对应修改后的代码执行流程:
- 同步代码依旧先输出
Starting... ... - 注册异步任务时:
setImmediate回调还是进入check阶段队列- Bluebird Promise的
then回调被加入nextTickQueue(比process.nextTick()的回调更早加入) process.nextTick()回调随后加入nextTickQueue
- 同步代码结束后,清空
nextTickQueue时会按添加顺序执行:- 先执行Bluebird Promise的回调,输出
Promise - 再执行
process.nextTick()的回调,输出process.nextTick()
- 先执行Bluebird Promise的回调,输出
- 最后进入
check阶段执行setImmediate
所以此时的输出顺序会变成:
Starting... ... Promise process.nextTick() setImmediate
另外补充一点:Bluebird还做了递归调用的优化——如果Promise链过长,它会自动切换到setImmediate来调度回调,避免nextTickQueue堆积导致的栈溢出。这种极端情况下,Promise回调会和setImmediate同属宏任务队列,输出顺序会变成Starting... ... → process.nextTick() → setImmediate → Promise,不过你的简单示例不会触发这个逻辑。
内容的提问来源于stack exchange,提问作者box_ch

