接口超时与自动重试怎么设计,才不会重复下单或产生僵尸请求?
先理解原理和示例,再练习完整口述与追问。
回答要点
单次超时与总截止时间分开;AbortController 真正发出取消信号;重试条件受幂等性约束;指数退避加抖动;区分取消原因。
原理拆解
先定义单次请求超时、整体重试总时长、最大次数和允许重试的错误类型。fetch 可以通过 AbortController.signal 取消本地请求处理,定时器到期时 abort,并在 finally 中清理;只用 Promise.race 返回超时错误,不会自动停止另一边请求,旧响应与副作用可能继续发生。即使客户端已 abort,也不能证明服务器撤销了已开始的操作。
GET 等符合幂等语义的请求可以在可恢复的网络错误、部分 5xx 或限流下有限重试;429、503 等还应尊重 Retry-After。参数错误、权限不足、明确的业务失败和用户主动取消不应无条件重试。下单、支付等操作必须与服务端约定幂等键和结果查询,网络超时时先确认处理结果,避免新建另一个订单。
重试采用有上限的指数退避并加入随机抖动,避免所有客户端同时冲击服务。每一轮检查整体截止时间和外部取消信号,为页面切换、组件销毁和用户搜索新条件提供取消路径;请求序号或状态版本还可防止旧结果覆盖新结果。日志记录真实尝试次数、取消原因与总耗时,向用户展示可理解的失败状态,并保留手动重试入口。不能为了“成功率”无限重试到页面失去响应。
示例与推演
一次请求限 3 秒、最多两次重试且总预算 5 秒:第二轮不能再无条件给满 3 秒,需要按剩余总预算计算。用户主动取消应立即停止,不当成网络故障重试;服务端返回 429 可尊重 Retry-After 并加抖动,但不能超过整体截止时间。
易错点
超时只说明客户端没有及时拿到确认,不证明服务器没执行。Promise.race 不取消另一分支,写操作应与服务端幂等和结果查询配合。
参考回答可直接练习口述
先定义单次请求超时、整体重试总时长、最大次数和允许重试的错误类型。fetch 可以通过 AbortController.signal 取消本地请求处理,定时器到期时 abort,并在 finally 中清理;只用 Promise.race 返回超时错误,不会自动停止另一边请求,旧响应与副作用可能继续发生。即使客户端已 abort,也不能证明服务器撤销了已开始的操作。