使用async/await配合指数退避与错误分类可构建健壮重试请求包装器,核心是控制重试时机、避免盲目重试并保留原始错误上下文;需区分网络错误、5xx服务端错误等可重试类型,跳过4xx客户端错误,并用AbortController防堆积。

用 async/await 搭配指数退避(exponential backoff)和错误分类,就能写出健壮的重试型异步请求包装器。核心是控制重试时机、避免盲目重试、保留原始错误上下文。
基础结构:封装 fetch 并支持重试次数与延迟
先定义一个可配置的函数,接收 URL、选项、最大重试次数和初始延迟(毫秒):
- 每次失败后等待时间按指数增长(如 100ms → 200ms → 400ms)
- 仅对网络错误或特定 HTTP 状态码(如 502/503/504)重试,4xx 错误通常不重试
- 用
AbortController防止请求在重试期间堆积
处理可重试错误:区分网络异常与业务错误
不是所有失败都该重试。需主动判断错误类型:
-
网络错误(
TypeError: failed to fetch或AbortError)—— 可重试 - 5xx 响应(服务端临时故障)—— 可重试
- 401/403(鉴权问题)—— 一般不重试,应刷新 token 后再发新请求
- 400/404/422(客户端问题)—— 不重试,直接抛出
实际代码示例(TypeScript + fetch)
以下是一个轻量但完整的实现:
async function retryFetch(
input: RequestInfo,
init: RequestInit = {},
options: {
maxRetries?: number;
baseDelayMs?: number;
} = {}
) {
const { maxRetries = 3, baseDelayMs = 100 } = options;
let lastError;
<p>for (let i = 0; i <= maxRetries; i++) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10_000);</p><pre class="brush:php;toolbar:false;">try {
const res = await fetch(input, {
...init,
signal: controller.signal,
});
clearTimeout(timeoutId);
if (res.ok) return res;
if (res.status >= 500 || [502, 503, 504].includes(res.status)) {
if (i === maxRetries) throw new Error(`HTTP ${res.status}: ${res.statusText}`);
await new Promise(r => setTimeout(r, baseDelayMs * Math.pow(2, i)));
continue;
}
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
} catch (err) {
clearTimeout(timeoutId);
lastError = err;
if (i === maxRetries) break;
if (err instanceof TypeError && err.message.includes('fetch')) {
await new Promise(r => setTimeout(r, baseDelayMs * Math.pow(2, i)));
} else {
throw err;
}
}}
throw lastError; }
进阶建议:可组合、可监控、可取消
生产环境可进一步增强:
- 把重试逻辑抽成中间件,配合 Axios 或自定义 client 使用
- 注入日志钩子(如每次重试前打点:
console.debug(`Retry #${i} for ${url}`)) - 支持外部传入 abortSignal,让上层统一控制整个请求链路生命周期
- 对敏感操作(如支付提交)禁用重试,或只重试幂等请求(GET/HEAD/OPTIONS/PUT)

















