服务器停止或Jest测试结束时如何处理setTimeout?
解决Jest测试因延迟定时器无法退出的问题及定时器管理方案
一、Jest测试中的处理方式
1. 用假定时器强制执行所有延迟任务
在测试文件中启用Jest假定时器,测试完成后立即触发所有待执行的setTimeout,既验证清理逻辑又避免Jest等待:
// 测试前启用假定时器 beforeAll(() => { jest.useFakeTimers(); }); test('交换令牌后正确清理刷新令牌', async () => { // 发送API请求 await request(app).post('/api/exchange-refresh-token').send({ refreshToken: 'test-token' }); // 立即执行所有延迟任务 jest.runAllTimers(); // 验证令牌已被移除 const tokenExists = await checkRefreshTokenInDB('test-token'); expect(tokenExists).toBe(false); }); // 测试结束后恢复真实定时器 afterAll(() => { jest.useRealTimers(); });
2. 直接清理未执行的定时器
如果测试不需要验证清理逻辑,可在测试后直接清除所有待处理定时器,让Jest正常退出:
afterEach(() => { jest.clearAllTimers(); });
二、服务器停止时的处理方案
在生产环境中,监听进程退出信号,主动处理未完成的延迟任务,避免数据不一致:
1. 保存定时器引用,退出时批量处理
在API逻辑中记录所有定时器ID,收到退出信号时直接执行清理逻辑:
// 全局存储待处理的定时器及关联令牌 const pendingCleanups = []; app.post('/api/exchange-refresh-token', async (req, res) => { const refreshToken = req.body.refreshToken; // 生成新令牌逻辑... const timerId = setTimeout(async () => { await removeUsedRefreshToken(refreshToken); // 执行后从数组移除 pendingCleanups = pendingCleanups.filter(item => item.timerId !== timerId); }, 5000); pendingCleanups.push({ timerId, refreshToken }); res.status(200).json({ accessToken: 'new-access-token' }); }); // 监听进程退出信号 process.on('SIGINT', async () => { console.log('服务器关闭中,处理待清理的刷新令牌...'); // 遍历所有待处理任务,直接执行清理 for (const item of pendingCleanups) { clearTimeout(item.timerId); await removeUsedRefreshToken(item.refreshToken); } process.exit(0); });
2. 用任务队列替代原生定时器
如果需要更可靠的延迟任务管理,可改用内存队列统一处理,退出时批量执行:
const cleanupQueue = []; app.post('/api/exchange-refresh-token', async (req, res) => { const refreshToken = req.body.refreshToken; // 生成新令牌逻辑... // 将清理任务加入队列 const task = () => removeUsedRefreshToken(refreshToken); cleanupQueue.push(task); // 延迟执行任务 setTimeout(() => { const index = cleanupQueue.indexOf(task); if (index !== -1) { cleanupQueue.splice(index, 1); task(); } }, 5000); res.status(200).json({ accessToken: 'new-access-token' }); }); // 退出时执行所有队列任务 process.on('SIGTERM', async () => { for (const task of cleanupQueue) { await task(); } process.exit(0); });
三、这类场景的最佳实践
- 测试环境优先用假定时器:完全控制时间流,避免测试等待耗时,同时保证逻辑验证准确。
- 生产环境监听进程信号:Node.js进程关闭时会收到
SIGINT/SIGTERM信号,此时必须处理未完成的延迟任务,防止数据不一致。 - 解耦定时器与业务逻辑:用队列或任务管理工具统一处理延迟任务,方便监控和控制,避免依赖原生
setTimeout导致的管理混乱。 - 及时清理定时器引用:执行完成或退出时清除定时器ID,避免内存泄漏。
内容的提问来源于stack exchange,提问作者ZiiMakc
相关产品推荐
相关产品推荐

