实现带超时时间和超时次数限制的 Promise 重试。
未标记
考查重点:超时计数、重试与底层取消。给每次任务包超时计时器,成功清理计时器,超时后按约定计数重试;超时不等于底层任务已取消
本题目录
参考答案
本答案基于相关资料,由 AI 辅助编写,供学习与面试准备参考。如有疏漏,欢迎反馈指正。
核心回答
每次尝试都调用任务工厂,创建新的 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重试