← 中文资源

简体中文版本

Retry-After 头退避

把 Retry-After 头解析为秒数或 HTTP 日期,并在下一次重试前对等待时长设上限。

阅读英文原文

功能说明

该示例让重试遵循服务端给出的节奏。服务端在 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;
}