如何编写一个自动重试的异步函数包装器

作者:袖梨 2026-06-17
可使用高阶函数封装异步重试逻辑,支持最大重试次数、指数退避延迟、错误过滤(shouldRetry)、泛型签名透传、AbortSignal取消及超时控制。

可以使用一个高阶函数封装异步操作,加入重试逻辑、延迟策略和错误过滤,核心是用 await + for 循环控制重试次数,并在每次失败后按策略等待。

支持最大重试次数与基础延迟

最简实用版本需指定最大尝试次数(maxRetries)和初始延迟(baseDelayMs),失败后递增等待时间(如指数退避),避免高频重试压垮服务。

  • 每次执行前检查是否已达重试上限,是则直接抛出最后一次错误
  • 捕获异常后,用 await new Promise(r => setTimeout(r, delay)) 实现等待
  • 延迟可设为固定值,也可按重试次数指数增长:baseDelayMs * 2 ** attempt

允许跳过特定错误类型

不是所有错误都值得重试,比如 401(未授权)、404(资源不存在)或业务校验失败,应立即终止重试。

  • 接收一个 shouldRetry 函数,入参为 error,返回 true 表示继续重试
  • 常见判断方式:检查 error.statuserror.codeerror.message 是否匹配预期可恢复错误(如网络超时、502/503/504)
  • shouldRetry(error) === false,立刻 throw error

保留原始函数签名与上下文

包装器应是泛型的,不侵入业务逻辑,调用方式与原函数完全一致。

  • 使用 TypeScript 泛型推导参数类型和返回值类型:async function retry<t>(fn: (...args: any[]) => Promise<t>, ...)</t></t>
  • 内部用 fn.apply(thisArg, args) 保证 this 和参数透传,兼容对象方法调用
  • 返回一个新函数,调用时自动触发带重试的执行流程

可选:支持取消与超时控制

长时间重试可能阻塞用户操作,建议集成 AbortSignal 或自定义 timeout。

  • 传入 signal:在每次 await 前检查 signal.aborted,提前退出
  • 传入 timeoutMs:记录起始时间,每次循环检查总耗时,超时即抛出 TimeoutError
  • 注意:AbortSignal 需配合支持它的异步操作(如 fetch(signal)),否则仅用于控制重试流程本身

相关文章

精彩推荐