实现带超时时间和超时次数限制的 Promise 重试。

考查重点:超时计数、重试与底层取消。给每次任务包超时计时器,成功清理计时器,超时后按约定计数重试;超时不等于底层任务已取消

本题目录

参考答案

核心回答

每次尝试都调用任务工厂,创建新的 Promise,让它与超时 Promise 竞争;超时就累计次数并按约定决定是否重试,成功或退出时清理计时器。不能反复等待同一个已经创建的 Promise,那不会重新发起任务。

题干没有说明“超时次数”是不是总重试次数。下面约定 maxTimeouts 表示允许出现的超时总数:设为三,第三次超时立即失败;普通业务错误直接抛出。若面试官要求“最多重试三次”,应另设 maxRetries,首次尝试不计入重试数。

JavaScript 实现

任务工厂接收每次独立的 AbortSignal。定时器先决定超时结果,再请求取消底层任务;只有本次计时器触发的超时才计入预算。

class TimeoutError extends Error {
  constructor() {
    super("attempt timed out");
    this.name = "TimeoutError";
  }
}
async function retryTimeout(factory, { timeoutMs, maxTimeouts }) {
  if (
    typeof factory !== "function" ||
    !Number.isFinite(timeoutMs) ||
    timeoutMs < 0 ||
    !Number.isInteger(maxTimeouts) ||
    maxTimeouts < 1
  ) {
    throw new TypeError("invalid factory or timeout options");
  }
  let timeoutCount = 0;
  while (true) {
    const controller = new AbortController();
    let timer;
    let timedOut = false;
    try {
      const timeout = new Promise((_, reject) => {
        timer = setTimeout(() => {
          timedOut = true;
          const error = new TimeoutError();
          reject(error);
          controller.abort(error);
        }, timeoutMs);
      });
      const task = Promise.resolve().then(() => factory(controller.signal));
      return await Promise.race([task, timeout]);
    } catch (error) {
      if (!timedOut || !(error instanceof TimeoutError)) throw error;
      timeoutCount++;
      if (timeoutCount >= maxTimeouts) throw error;
    } finally {
      clearTimeout(timer);
    }
  }
}

怎样使用与理解取消

const result = await retryTimeout(
  (signal) =>
    fetch("/api/report", { signal }).then((r) => {
      if (!r.ok) throw new Error(`HTTP ${r.status}`);
      return r.json();
    }),
  { timeoutMs: 3000, maxTimeouts: 3 }
);

把 r.json() 包进工厂返回的链,超时才覆盖读取和解析响应,而不只覆盖收到响应头之前的等待。平台或SDK可能另有超时和取消配置,需核对它们覆盖的是连接、完整响应还是整次任务。

Promise.race() 本身只选择最先完成者,不会终止其他任务。abort() 也是协作式取消:工厂忽略信号时,旧尝试仍可能在后台继续,下一次重试就可能与它重叠;服务端收到请求后是否停止也不能由客户端保证。带写入副作用的重试要复用业务幂等标识,必要时先查询原操作状态。

真实远程调用通常还需总截止时间、退避与抖动,并区分可以重试的网络错误和不可重试的业务错误。题目只约定超时重试,不能把所有异常都无条件重试。

参考资料

  • 国内:阮一峰:Promise对象:采用 Promise.race() 与超时竞争的方式,并补充未胜出的任务不会自动取消这一边界。
  • 国内:阮一峰:async函数:采用 await 配合 try/catch 组织逐次尝试与错误传播;定时器在每次尝试结束后清理,避免下一次尝试受到旧计时器影响。
  • 国内:阿里云:JavaScript SDK上传文件:采用向任务传入AbortSignal实现协作取消的接口方式,区别取消与恢复/重新尝试。
  • 国内:腾讯云:Fetch:比较平台提供的请求超时配置与应用层整次尝试截止时间,避免把平台扩展当作浏览器通用参数。
  • 国外:MDN:Promise.race:核对首个完成结果决定race状态,重复尝试必须创建新任务。
  • 国外:WHATWG:AbortController:核对AbortSignal与abort原因的传递,说明取消依赖底层操作配合。

相关面经

1 篇

百度

前端开发工程师(Agent)
实习二面
查看原面经 →
带超时时间和超时次数的promise重试

相关题目

4 题