一次网络请求的边界:超时、状态码与安全重试
用浏览器原生 fetch 做一个小练习:明确何时失败,区分网络与业务错误,给重试留出合理边界。

网络请求可能很慢,也可能返回你没有预料到的状态。把“等待响应”“HTTP 成功”和“数据可用”拆开理解,比把所有异常都显示成“网络错误”更有帮助。
fetch 不会替你判断 HTTP 成功
fetch 通常在网络层错误时拒绝 Promise,但 HTTP 404 或 500 仍然会返回 Response。你需要自行检查 response.ok。它表示状态码处于 200–299 范围,不表示返回内容一定符合业务契约。
async function loadNotes(url: string): Promise<unknown> {
const response = await fetch(url, {
signal: AbortSignal.timeout(8000),
headers: { Accept: 'application/json' },
});
if (!response.ok) {
throw new Error(`HTTP 请求失败:${response.status}`);
}
return response.json();
}
这段例子适用于支持 AbortSignal.timeout() 的现代浏览器。旧环境需要用 AbortController 与定时器实现,或提供兼容方案。实际项目还需要对返回数据做运行时校验。
超时控制的是客户端等待
超时让界面避免无限等待,但不一定撤销已经开始的服务端操作。对于 AI 生成、支付等请求,客户端没收到成功结果,并不能据此认定服务端没有执行。
把超时与主动取消作为独立状态展示,有助于用户判断是否需要检查任务历史,而不是马上重复提交。
重试前先判断能否重复执行
读取公开文章的 GET 请求,通常比创建订单的 POST 请求更适合重试。对于写操作,是否支持幂等键、服务端如何去重、重复操作会产生什么影响,都应由接口契约说明。
对于 429,可根据服务端的 Retry-After 提示与实际规则延迟再试;对于临时 5xx,可考虑有限次数的指数退避和随机抖动。不要对每一种失败都自动重试,也不要无限循环。
一个可执行的排查顺序
- 确认请求地址、方法与跨域配置。
- 检查 HTTP 状态和服务端请求标识。
- 检查响应 Content-Type 与解析错误。
- 区分超时、用户取消和实际业务拒绝。
- 记录必要的诊断字段,不把密钥、完整敏感正文写入日志。
把错误分清楚以后,界面提示才有意义:地址错误就修地址,鉴权失败就检查授权,限流就等待。一个笼统的“请重试”,往往只会制造更多请求。

