【背景】
做了 10 年前端,最近帮团队做稳定性梳理时,翻了一遍所有微前端子应用的网络层配置,发现一个让我很意外的事实:
- 后端团队有完整的超时预算体系:网关层 30s、服务间 RPC 500ms、DB 查询 200ms,层层传递,还有熔断降级。
- 而前端侧:axios 的 timeout 字段,3 个子应用里 2 个没设(走默认 Infinity);1 个设了 10s 但没配 retry;fetch 更惨——原生 fetch 根本没有超时机制,全靠 AbortController 手动拼。
也就是说,后端花了大量精力做的"超时预算",到了浏览器这一端就断了链。用户点完按钮后,到底是 5s 还是 30s 还是永远转圈,完全取决于前端同学"有没有想起来设一下"。
这件事的可观测性代价也不小:后端 APM 看到的是"请求 200 OK,耗时 6.2s",前端错误监控看到的是"timeout",用户看到的是"网络异常,请重试"。三段日志拼不出一个完整故事——因为前端的"timeout"和后端 6.2s 的"成功"之间,没有任何一方记录了"这个 6.2s 对前端来说到底算不算超时"。
【常见分歧 / 几种做法】
我调研了几个团队,大致分四派:
派 A:前端自治 超时和重试是"体验层"的决策,不同接口对延迟的敏感度不同(搜索 3s 该提示"还在找",支付 10s 该有倒计时),前端最了解 UI 状态,所以超时策略应该由前端自己定。后端只负责在合理时间内返回。
派 B:后端声明,前端遵从
后端在响应头里加 X-Timeout-Budget: 3000,前端读这个值来设超时。好处是超时策略跟后端实际耗时对齐,不会出现"后端 5s 能返回但前端 3s 就断了"的错配。
派 C:统一网关层兜底 前端不管超时,全部交给网关(Nginx / API Gateway)的 proxy_read_timeout。前端只关心"收到响应了没有"。
派 D:前端有 base 值 + 按接口覆盖 设一个全局默认超时(比如 8s),但对特定接口(上传、聚合查询)单独覆盖。重试策略也分接口:幂等 GET 自动重试 1 次,POST 绝不自动重试。
【我的倾向与理由】
我倾向 派 D + 部分派 B,理由如下:
-
超时策略本质上是一个"体验 × 成本"的权衡,前端有信息优势。 后端不知道用户当前是 Wi-Fi 还是 4G,不知道页面是不是在后台 tab,不知道这个按钮是"查询"还是"提交订单"。这些上下文只有前端有。
-
但后端应该"建议"而不是"命令"。 我更喜欢在 OpenAPI spec 里声明每个接口的
recommended_timeout_ms,前端默认读取这个值作为起点,但允许按场景覆盖。这比响应头方案轻量——响应头意味着前端必须在请求前就知道这个值(鸡生蛋问题),而 OpenAPI 是静态的、可以提前拿到的。 -
重试策略必须区分幂等性,这是红线。 GET 可以安全重试 1 次;POST 如果后端没有幂等键,前端自动重试等于 double-submit。这条规则应该写进团队的 API 契约文档,而不是靠每个前端同学"记得"。
-
落地成本。 派 B 的响应头方案需要后端每个接口都加 header,前端每次请求都要读 header 再设超时——链路变长了,而且响应头方案对 fetch 的 preflight 请求(CORS)还有额外复杂度。派 D 只需要前端维护一个接口超时配置表 + 全局默认值,一周内可以落地,不需要后端改代码。从收益/成本比来看,派 D 是最先该做的那一步。
【想听大家在什么场景下怎么选】
想征集几个具体场景的看法,尤其是后端同学和 SRE 同学的视角:
-
聚合接口(一次请求扇出 5+ 个后端调用,后端耗时方差极大,P50 800ms 但 P99 可能 5s+):前端超时设多少合理?设短了误杀慢请求,设长了用户干等。你们团队是怎么切的?
-
WebSocket / SSE 长连接:心跳超时 vs 业务超时,前端该用哪种机制?AbortController 对 SSE 的兼容性你们踩过坑吗?
-
弱网环境(4G/3G/电梯/地铁):前端该不该检测到网络质量变化(navigator.connection / Network Information API)后动态调整超时?还是保持固定值更可控?
-
你团队现在的做法是哪种派? 有没有踩过"前端超时 < 后端实际耗时"导致用户看到错误但后端其实成功了的坑?(我踩过:用户看到"网络异常",重试后又成功了,后端日志显示两次都 200——因为前端第一次的超时其实没真正断开 TCP 连接。)
欢迎拍砖,尤其是后端同学——你们觉得"建议前端超时"这件事该不该写进 API 契约?OpenAPI 的 x-recommended-timeout 扩展你们用过吗?