直接用 async/await 套 for 循环重试不靠谱,因缺乏错误类型过滤(如误重试401)、无最大重试次数导致卡死、无退避延迟引发限流;retryAsync 闭包封装重试策略,支持指数退避与错误判断。

为什么直接用 async/await 套 for 循环重试不靠谱
因为网络抖动或临时性错误(比如 503 Service Unavailable、NetworkError)往往只持续几秒,但硬写循环容易忽略错误类型判断、超时控制和退避策略。更关键的是:如果重试逻辑散落在每个调用点,后续要加日志、监控或熔断就只能改业务代码。
- 没做错误过滤:把
401 Unauthorized也重试,纯属浪费请求 - 没设最大重试次数:某个服务永久挂了,任务会卡死在无限循环里
- 没加退避延迟:连续打 5 次失败请求,可能触发对方限流甚至封 IP
retryAsync 闭包的典型结构长什么样
核心是返回一个新函数,它“记住”了原始函数、重试配置,并在每次调用时按需执行。不是一上来就跑任务,而是封装行为。
const retryAsync = (fn, { maxRetries = 3, baseDelay = 100, jitter = true } = {}) => {
return async function (...args) {
let lastError;
for (let i = 0; i <= maxRetries; i++) {
try {
return await fn(...args);
} catch (err) {
lastError = err;
if (i === maxRetries) break;
const delay = baseDelay * Math.pow(2, i) + (jitter ? Math.random() * 100 : 0);
await new Promise(r => setTimeout(r, delay));
}
}
throw lastError;
};
};-
fn是原始异步函数,比如fetchUser或sendToKafka - 闭包捕获了
maxRetries和baseDelay,后续每次调用都复用这套策略 - 指数退避公式
baseDelay * 2^i防止雪崩,jitter加随机值避免重试请求扎堆
怎么过滤不该重试的错误类型
不能所有异常都重试。比如 400 Bad Request 是客户端问题,重试一百次也没用;而 503 或 TypeError: failed to fetch 才值得等一等再试。
- 在
catch块里加判断:检查err.status(如果是Response对象)、err.name(如TypeError)、或正则匹配err.message - 推荐把判断逻辑抽成独立函数传入,比如
shouldRetry: (err) => err.status >= 500 || err.name === 'TypeError' - 注意:Node.js 的
AbortError(超时中断)通常不该重试,否则可能掩盖真正的超时配置问题
实际用的时候最容易漏掉哪三件事
闭包让代码看着干净,但真实压测或上线后常因这几个点翻车:
- 没给原始函数加
signal支持:重试期间旧请求还在跑,造成资源泄漏。要用AbortController统一控制生命周期 - 没透传
this上下文:如果fn是类方法,直接retryAsync(instance.fetchData)会导致this变成undefined,得用fn.bind(instance)或箭头函数包装 - 没处理并发重试:多个并行调用共享同一个闭包状态?不,每个
retryAsync(fn, opts)调用都生成独立闭包,但如果你把重试器挂到类实例上又复用,就可能意外共享状态
真正麻烦的从来不是写闭包,而是想清楚哪些状态该被“记住”,哪些必须每次调用都重置。

















