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

作者:技术拾光

时间:2026-06-17

浏览:155

聊到异步重试,其核心思路其实并不复杂:通过一个高阶函数来封装异步操作,自动加入重试次数、延迟策略、错误过滤等控制逻辑。最底层的手段,无非就是 await 配合 for 循环来管理重试次数,每次失败后按既定策略等一等再尝试下次。 支持最大重试次数与基础延迟 先看最实用的简化版本,它需要两个参数:最大尝

文章目录

聊到异步重试,其核心思路其实并不复杂:通过一个高阶函数来封装异步操作,自动加入重试次数、延迟策略、错误过滤等控制逻辑。最底层的手段,无非就是 await 配合 for 循环来管理重试次数,每次失败后按既定策略等一等再尝试下次。

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

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

先看最实用的简化版本,它需要两个参数:最大尝试次数(maxRetries)和初始延迟(baseDelayMs)。每次失败后递增等待时间,比如采用指数退避策略,避免高频重试瞬间压垮目标服务。具体操作上:

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

允许跳过特定错误类型

并不是所有错误都值得重试。典型情况如 401(未授权)、404(资源不存在)或业务校验失败,这种时候应该立即终止重试。所以包装器需要接收一个 shouldRetry 函数,它接收错误对象,返回 true 表示值得继续尝试,返回 false 则立即 throw error。常见的判断逻辑包括检查 error.status、error.code 或 error.message,是否匹配可恢复的错误类型(比如网络超时、502/503/504)。

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

包装器不应侵入业务逻辑,它对调用者来说应当透明——调用方式与原函数完全一致。借助 TypeScript 泛型,可以自然推导出参数类型和返回值类型,比如 async function retry(fn: (...args: any[]) => Promise, ...)。内部通过 fn.apply(thisArg, args) 来保证 this 和参数的透传,这尤其关键——它能兼容对象方法调用。最后,返回一个全新函数,调用时自动进入带重试的执行流程。

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

长时间的重试可能阻塞用户操作,因此有必要集成 AbortSignal 或自定义的 timeout 机制。具体来说,可以传入 signal,在每次 await 前检查 signal.aborted,提前退出;传入 timeoutMs 时,记录起始时间,每次循环检查总耗时,超时立即抛出 TimeoutError。需要留意的是,AbortSignal 只有配合支持它的异步操作(比如 fetch(signal))才能发挥完整作用,否则它仅用于控制重试流程本身的取消。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›