fake-timers结合真实HTTP请求时异步代码等待问题求助
fake-timers 结合真实HTTP请求时的异步等待问题
问题场景
我需要将fake-timers模拟的离散时间事件与真实HTTP请求结合使用,示例代码如下:
const FakeTimers = require("@sinonjs/fake-timers"); const clock = FakeTimers.install(); const axios = require('axios'); let fun = async () => { setTimeout(async function () { console.log(+new Date()+' Timer for 1000ms fired'); await axios.get('https://jsonplaceholder.typicode.com/todos/1'); console.log(+new Date() + ' Response'); }, 1000); setTimeout(function () { console.log(+new Date()+' Timer for 2000ms fired'); }, 2000); // Tick the clock to 1000ms and wait for any operations to finish. await clock.tickAsync(1000); // Tick the clock to 2000ms and wait for any operations to finish. await clock.tickAsync(1000); } fun();
预期输出
1000 Timer for 1000ms fired 1000 Response 2000 Timer for 2000ms fired
实际输出
1000 Timer for 1000ms fired 2000 Timer for 2000ms fired 2000 Response
想知道有没有办法让fake-timers等待真实异步代码(比如HTTP请求)执行完成后,再推进时间?
问题原因
fake-timers 的 tickAsync 只会等待它自身模拟的异步操作(比如模拟的 setTimeout、Promise.resolve 等),但真实的HTTP请求是由Node.js/浏览器的原生事件循环处理的,不属于fake-timers的模拟体系,所以tickAsync不会等待HTTP请求完成,直接推进了时间,导致第二个定时器先触发,之后HTTP响应才回来。
解决方案
核心思路是手动追踪真实异步操作的状态,等它完成后再推进fake-timers的时间。
方案1:追踪HTTP请求的Promise
修改代码,把第一个定时器里的HTTP请求Promise暴露出来,在tickAsync(1000)之后等待这个Promise完成,再继续推进时间:
const FakeTimers = require("@sinonjs/fake-timers"); const clock = FakeTimers.install(); const axios = require('axios'); let fun = async () => { let requestPromise; // 用于存储HTTP请求的Promise setTimeout(async function () { console.log(+new Date()+' Timer for 1000ms fired'); requestPromise = axios.get('https://jsonplaceholder.typicode.com/todos/1'); await requestPromise; console.log(+new Date() + ' Response'); }, 1000); setTimeout(function () { console.log(+new Date()+' Timer for 2000ms fired'); }, 2000); // 推进到1000ms,触发第一个定时器 await clock.tickAsync(1000); // 等待真实HTTP请求完成 await requestPromise; // 再推进1000ms到2000ms await clock.tickAsync(1000); } fun();
方案2:使用clock.runAllAsync配合信号量
如果有多个异步操作需要等待,可以用一个信号量来标记所有真实异步操作完成,再停止fake-timers的运行:
const FakeTimers = require("@sinonjs/fake-timers"); const clock = FakeTimers.install(); const axios = require('axios'); let fun = async () => { let isRequestDone = false; setTimeout(async function () { console.log(+new Date()+' Timer for 1000ms fired'); await axios.get('https://jsonplaceholder.typicode.com/todos/1'); console.log(+new Date() + ' Response'); isRequestDone = true; }, 1000); setTimeout(function () { // 只有当HTTP请求完成后才触发这个定时器的逻辑 if (!isRequestDone) { // 重新安排定时器,直到请求完成 setTimeout(this, 100); return; } console.log(+new Date()+' Timer for 2000ms fired'); }, 2000); // 运行所有模拟的定时器,直到没有待处理的模拟任务 await clock.runAllAsync(); } fun();
说明
两种方案中,方案1更直接,适合单个明确的异步操作;方案2适合复杂场景,通过重新调度模拟定时器来等待真实异步操作完成。
内容的提问来源于stack exchange,提问作者CornFlow
相关产品推荐
相关产品推荐

