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

服务器停止或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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 23:25:20