功能说明
该示例让重试遵循服务端给出的节奏。服务端在 429 或 503 响应里用 Retry-After 指定等待时长,可能是一个秒数,也可能是一个 HTTP 日期。解析结果被用作下次尝试前的延迟,并设有上限,防止异常大的值让客户端长时间停摆。
关键代码
解析先去除空白,纯数字按秒处理并乘以一千,日期形式则用 Date.parse 解析为绝对时间,再与当前时间相减。未来时间与过去时间都要处理:过去时间取零立即重试,未来时间则受 maxDelayMs 截断。取不到或非法时回退到默认延迟。重试循环只对 429 与 5xx 重试,成功或 4xx 直接返回,避免对不可恢复错误白白重试。
使用注意
重试必须幂等或可安全重复,写入型请求需要幂等键,否则一次超时可能变成两次副作用。缺少 Retry-After 时退避量应由调用方以指数退避加抖动给出,避免众多客户端同步重试形成惊群。上限截断可能早于服务端要求的时间,此时应按 5xx 语义处理而不是无限等待;同时受总超时约束,使整次调用有界。
代码示例
export function retryDelay(header, { now = Date.now(), maxDelayMs = 60000, fallbackMs = 1000 } = {}) {
if (header === undefined || header === null || String(header).trim() === '') return fallbackMs;
const raw = String(header).trim();
if (/^\d+$/.test(raw)) {
return Math.min(Number(raw) * 1000, maxDelayMs);
}
const when = Date.parse(raw);
if (Number.isNaN(when)) return fallbackMs;
return Math.min(Math.max(when - now, 0), maxDelayMs);
}
export async function fetchWithRetry(url, options = {}, maxAttempts = 3) {
let lastError;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
const res = await fetch(url, options);
if (res.status !== 429 && res.status < 500) return res;
if (attempt === maxAttempts) return res;
lastError = res;
const delay = retryDelay(res.headers.get('retry-after'));
await new Promise((resolve) => setTimeout(resolve, delay));
}
throw lastError;
}