Jest 26中jest.runAllTicks未完全耗尽微任务队列问题
问题根因
你遇到的现象由两个核心机制共同导致:
1. 未被await的独立Promise链微任务排序
代码里B().then(F)没有被await,是和A返回值无关的独立Promise链。当同步代码执行到await A()这一行时,V8原生微任务队列的初始顺序是:
- 队首:
E回调,来自D().then(E),是最早注册的已resolve Promise的回调 - 队中:test函数
await A()之后的恢复逻辑,也就是jest.runAllTicks()、console.log(arr)这部分代码
同步执行栈清空后引擎开始按顺序跑微任务:
- 先执行
E,往数组推入'E'。E返回Promise.resolve()后,引擎会往队列末尾追加一个新微任务:等E返回的Promise resolve完成,再把B()返回的Promise标记为resolve,之后将F注册为then回调入队。 - 此时微任务队列顺序变为:[test函数恢复逻辑, 触发F入队的处理逻辑]
- 接下来引擎执行test函数恢复逻辑:先调用
jest.runAllTicks(),紧接着执行console.log(arr)。这时候F和X对应的回调还排在队列后面没执行,自然不会出现在日志里。等console.log打印完成后,引擎才会继续处理后续微任务执行F、X,但已经过了日志打印的时机。
2. Jest 26版本jest.runAllTicks()的能力边界
Jest 26的jest.useFakeTimers()默认不会mock全局Promise实现,jest.runAllTicks()只会清空Jest自己维护的mock微任务队列(比如被mock的process.nextTick、setImmediate回调),完全不会干涉V8引擎原生管理的Promise微任务队列,所以调用这个方法根本不会把排在队列后面的F、X相关微任务提前拉出来执行。
验证方法
如果在打印日志前额外等待两轮原生微任务,就能得到你预期的完整顺序:
test("test", async ()=>{ jest.useFakeTimers(); await A(); jest.runAllTicks(); // 等待两轮微任务消化B链上的回调 await Promise.resolve(); await Promise.resolve(); console.log(arr); // 输出 [ 'A', 'B', 'D', 'C', 'E', 'F', 'X' ] });
如果需要runAllTicks()真正递归清空所有微任务(包括Promise链执行过程中新入队的任务),建议升级到Jest 27及以上版本,新版本默认会接管Promise微任务队列,符合你对API行为的预期。
内容的提问来源于stack exchange,提问作者Ashish Yadav
相关产品推荐
相关产品推荐

