前端请求超时如何实现?Fetch 为什么没有内置 timeout?
简化版
Fetch 没有直接的 timeout 配置,通常用 AbortController 和 setTimeout 实现超时取消。超时不是简单报错,还要区分连接慢、服务端慢、用户取消、业务错误和网络断开。工程上要设置合理超时时间、清理 timer、避免重复提示、对幂等请求可重试,对非幂等请求谨慎处理。
详细版
基础封装如下:
async function fetchWithTimeout(url: string, options: RequestInit = {}, timeout = 10000) {
const controller = new AbortController()
const timer = setTimeout(() => controller.abort('timeout'), timeout)
try {
return await fetch(url, { ...options, signal: controller.signal })
} finally {
clearTimeout(timer)
}
}
| 点 | 说明 |
|---|---|
| AbortController | 取消 fetch |
| setTimeout | 到点触发 abort |
| finally | 清理定时器 |
| 错误分类 | 区分超时和其他异常 |
超时策略是用户体验和系统保护,不是让后端业务自动回滚的魔法。
完整版教学
一、为什么需要请求超时
网络请求可能因为弱网、服务端卡住、网关排队、连接异常而长时间不返回。如果没有超时,页面 loading 会一直转,用户不知道发生了什么。
超时可以让前端及时给出反馈,也能释放一些客户端资源。
二、Fetch 为什么没有 timeout 字段
Fetch 的设计更倾向于用 AbortController 统一处理取消,包括用户取消、路由切换、组件卸载和超时。
也就是说 timeout 是一种取消原因,不是独立的网络能力。
三、基础实现细节
实现时必须清理 timer。否则请求已经成功返回,定时器还在,后续触发 abort 会造成奇怪副作用或资源浪费。
try {
const res = await fetch(url, { signal })
return res
} finally {
clearTimeout(timer)
}
finally 是最稳妥的清理位置。
四、错误分类
请求失败不等于超时。
| 类型 | 示例 | 处理 |
|---|---|---|
| 超时 | 超过 10s | 提示重试或降级 |
| 用户取消 | 切页、关闭弹窗 | 通常静默 |
| HTTP 错误 | 500、404 | 看业务提示 |
| 网络断开 | offline、DNS | 提示网络异常 |
错误分类可以改善用户提示和监控质量。
五、超时时间怎么定
不同接口不应一刀切。
- 首屏关键查询:较短超时,失败给降级。
- 大文件上传:更长超时,配合进度和断点续传。
- 后台导出:可能改成异步任务轮询。
- 写操作:超时后要查询结果,不能简单重复提交。
六、和重试的关系
超时后是否重试取决于请求是否幂等。GET 查询通常可以重试;POST 创建订单、支付确认等要谨慎,最好有幂等键。
if (isTimeout(error) && method === 'GET') {
// 可以考虑重试
}
七、常见误区与追问
- 误区:超时就是请求没有到服务端。 请求可能已经到达并被处理,只是响应没回来。
- 误区:超时后可以无脑重试 POST。 写请求可能造成重复操作,必须有幂等设计。
- 误区:Promise.race 超时就够了。 只 race 不 abort,底层请求可能还在继续。
- 追问:为什么要 clearTimeout? 避免请求结束后定时器仍触发,造成资源浪费和误取消。
- 追问:如何区分用户取消和超时? 可以记录 abort reason 或封装不同错误类型。
- 追问:上传接口超时怎么设置? 上传耗时受文件大小和网络影响,应更长,并配合进度和断点续传。
八、加强记忆
请求超时记成“定时器触发 AbortController”。成功失败都清 timer;GET 可重试,POST 要幂等;超时只说明前端等不到,不代表后端没执行。