应通过promise缓存、abortcontroller取消机制和请求id幂等性三重保障防止并发重试:用排序后的url+method+body生成唯一key缓存promise;每次fetch绑定独立abortsignal,新请求前abort旧signal;后端校验x-request-id确保重复请求不改变状态。

重试本身不是问题,问题出在“多个重试同时发起”——比如用户狂点按钮、页面自动轮询、或错误地在循环里触发重试逻辑,导致同一请求被并发执行多次。关键是要让重试过程串行化、有状态、可取消,而不是放任多个 fetch 同时跑。
用 Promise 缓存锁定同一请求
对相同参数(URL + method + body)的请求,只允许一个真实网络调用在进行,其余等待者复用它的结果。这既防重复发送,也自然避免并发重试:
- 生成唯一 key:例如
method:POST|url:/api/order|body:{id:123},注意对 body 做 JSON.stringify + 排序(避免键顺序不同导致 key 不一致) - 维护一个
Map<string promise></string>,key 存在就直接 await 已有 Promise;key 不存在则新建 fetch 并存入 Map - 请求完成(无论成功或失败)后,从 Map 中删除该 key,允许后续新请求发起
重试必须带取消机制
如果第一次请求还没结束,用户又点了第二次,前一次重试流程应主动中止,避免两个重试链并行跑:
- 使用
AbortController,每次发起 fetch 都绑定独立 signal - 在重试函数内部,每次循环前检查 signal 是否已 abort;若已 abort,直接 reject
- 上层调用方在触发新请求前,先调用
abort()终止旧的 controller
幂等性 + 请求 ID 是后端兜底前提
前端再严谨也无法 100% 拦住所有并发重试,所以后端必须配合:
- 前端每次请求带上唯一
X-Request-ID(如 UUID),服务端记录该 ID 是否已处理过 - 对非幂等接口(如 POST 创建订单),后端根据 ID 返回 409 Conflict 或直接返回已有结果,而非重复执行
- 不要依赖前端不发重试,而是设计成“即使发了,也不影响最终状态”
避免在事件回调里无防护地调重试
这是最常见翻车点:按钮点击、表单提交、定时器触发重试时,没做节流或状态锁:
- 禁用按钮 + 状态标记:点击后设
isRetrying = true,重试结束才置为 false;期间点击直接 return - 不用
setTimeout手写重试,改用封装好的 retry 函数,它内部管理状态和 controller - 轮询类场景,用
setInterval+ 清除逻辑,而不是在响应回调里递归setTimeout
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











