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

Jest测试含setInterval异步调用的TS代码:After输出异常及解决

Jest测试含异步操作的setInterval代码时日志顺序异常问题

场景与代码

我需要用Jest测试一段包含setInterval且内部存在异步操作的TypeScript代码,先写出了正常运行的代码:

export { }

let startTime: number;
let intervalTimer: NodeJS.Timer;

async function f2(): Promise<void> {
    console.log(Date.now() - startTime);
}

async function f1(): Promise<void> {
    intervalTimer = setInterval(async () => {
        console.log("Before", Date.now() - startTime);
        await f2();
        console.log("After", Date.now() - startTime);
    }, 200);
}

startTime = Date.now();
setTimeout(() => { clearInterval(intervalTimer); }, 900);
f1();

这段代码运行后,控制台输出符合预期:

Before 201
202
After 202
Before 401
401
After 401
Before 602
602
After 602
Before 802
802
After 802

但编写Jest测试代码后,出现了异常:

let startTime: number;
let intervalTimer: NodeJS.Timer;

async function f2(): Promise<void> {
    console.log("f2", Date.now() - startTime);
}

async function f1(): Promise<void> {
    intervalTimer = setInterval(async () => {
        console.log("Before", Date.now() - startTime);
        await f2();
        console.log("After", Date.now() - startTime);
    }, 200);
}

describe('Suite1', () => {
    beforeEach(() => { jest.useFakeTimers(); });
    afterEach(() => { jest.useRealTimers(); });
    test('Case1', async () => {
        startTime = Date.now();
        setTimeout(() => { clearInterval(intervalTimer); }, 900);
        await f1();
        jest.advanceTimersByTime(900);
    });
});

控制台输出异常:所有Before和f2的日志依次输出后,所有After日志仅在最后集中输出,而非紧跟对应f2调用之后。

原因分析

Jest的jest.advanceTimersByTime()会一次性快进所有定时器,但异步函数的Promise回调会被推入微任务队列。当快进定时器时,所有setInterval的回调会被同步执行,执行到await f2()时,f2的同步日志会立即输出,但await后的console.log("After"...)会被放到微任务队列中。由于此时Jest还在同步处理所有定时器回调,微任务队列不会被清空,直到所有定时器回调执行完毕后,才会一次性处理所有微任务,导致所有After日志集中在最后输出。

解决方案

需要在每次定时器回调执行后,手动清空微任务队列,确保await后的代码能及时执行。可以通过在每次快进部分时间后,调用await Promise.resolve()来触发微任务队列的处理,或者分多次快进定时器并处理微任务。

修改后的测试代码如下:

describe('Suite1', () => {
    beforeEach(() => { jest.useFakeTimers(); });
    afterEach(() => { jest.useRealTimers(); });
    test('Case1', async () => {
        startTime = Date.now();
        setTimeout(() => { clearInterval(intervalTimer); }, 900);
        await f1();
        
        // 分多次快进,每次快进后处理微任务
        for (let i = 0; i < 4; i++) {
            jest.advanceTimersByTime(200);
            await Promise.resolve(); // 清空微任务队列,让After日志及时输出
        }
        jest.advanceTimersByTime(100); // 快进剩余的100ms触发clearInterval
    });
});

这样修改后,每次setInterval回调执行到await f2()后,微任务会被及时处理,After日志就能紧跟对应f2的日志输出,和正常运行时的顺序一致。

内容的提问来源于stack exchange,提问作者carlo.milanesi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 21:46:14