You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vitest假定时器处理嵌套异步定时器(setTimeout)的问题咨询

Vitest假定时器处理嵌套异步定时器(setTimeout)的问题咨询

我完全懂你碰到的这个坑——用Vitest假定时器测试这种循环嵌套的setTimeout时,普通的vi.runAllTimers()确实会漏掉后续动态创建的定时器,导致测试卡壳。

问题原因

vi.runAllTimers()的工作逻辑是一次性执行当前定时器队列里所有已存在的任务,但你的countDown函数里,第二个setTimeout是在第一个await sleep(1000)的回调执行完成后才被创建的。当你第一次调用vi.runAllTimers()时,第二个定时器还没被加入队列,所以根本不会被执行,这就导致countDown(2)返回的promise一直处于pending状态,测试就卡在await promise这一步了。

解决方案

针对这种动态创建定时器的场景,Vitest有专门的处理方式,下面给你两种可行的方案:

方案1:使用vi.runAllTimersAsync()(推荐)

Vitest提供了runAllTimersAsync()方法,它会先执行当前队列的定时器,然后等待所有异步操作完成(包括那些会创建新定时器的操作),直到没有更多待处理的定时器为止。修改你的测试代码如下:

it('works with runAllTimersAsync', async () => {
  const promise = countDown(2)
  await vi.runAllTimersAsync() // 改用runAllTimersAsync并await
  await promise // 现在不会卡住了
  // 这里可以添加你的断言逻辑,比如验证倒计时的输出次数、最终状态等
})

方案2:手动循环处理定时器(兼容旧版本)

如果你使用的Vitest版本没有runAllTimersAsync,可以手动循环调用runAllTimers,同时等待事件循环处理异步回调,直到promise完成:

it('works with manual timer looping', async () => {
  const promise = countDown(2)
  // 循环执行直到promise状态变为fulfilled
  while (promise.status !== 'fulfilled') {
    vi.runAllTimers()
    // 给事件循环时间处理异步回调,生成新的定时器
    await new Promise(resolve => setImmediate(resolve))
  }
  await promise
})

针对你实际场景的补充说明

你的真实需求是测试axios-retry的重试逻辑,本质和这个倒计时例子完全一致——重试的定时器是在前一次请求失败后才动态创建的。用runAllTimersAsync()就能完美解决这个问题,它会自动处理所有嵌套生成的重试定时器,让你的测试一次性跑完所有重试流程,不用等待真实时间。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 08:39:37