前端如何取消请求?为什么需要取消请求?
简化版
前端可以用 AbortController 取消 fetch 请求,也可以用 axios 的取消能力。取消请求常用于组件卸载、搜索输入切换、路由跳转、避免旧请求覆盖新结果。它能减少无效工作,也能解决请求竞态问题。
详细版
需要取消请求的场景:
- 用户离开页面,请求结果不再需要。
- 搜索框输入变化,旧关键词请求应取消。
- 路由快速切换,避免旧页面请求回来更新新页面。
- 上传下载需要用户主动取消。
fetch 示例:
const controller = new AbortController()
fetch(url, { signal: controller.signal })
controller.abort()
取消请求后通常会抛出 AbortError,需要和普通错误区分处理。
完整版教学
一、为什么取消请求重要
请求发出后,用户可能已经切换页面或改变输入。旧请求如果回来后继续更新状态,可能造成错误展示。
例如搜索框先搜 a,再搜 abc。如果 a 的请求更晚返回,可能覆盖 abc 的结果。这就是竞态。
取消旧请求或忽略旧响应都可以解决这个问题。
二、AbortController
AbortController 是现代浏览器取消异步操作的标准方式。它通过 signal 把取消信号传给 fetch。
当调用 abort() 时,fetch 会被中止,并进入 reject。
代码中要识别 AbortError,避免把用户主动取消当成异常弹错。
三、取消和忽略的区别
取消请求是尽量终止网络和后续处理。忽略响应是不一定终止请求,但返回后判断它是否已经过期,过期就不更新 UI。
有些请求已经到达服务器,前端 abort 不代表服务端一定停止处理。因此关键幂等和状态一致性仍要服务端保证。
四、面试追问与工程落地
面试官可能问:“组件卸载后请求返回 setState 报错怎么办?”
React/Vue 中可以在卸载时 abort 请求,或维护一个已卸载标记,返回后判断是否还允许更新。更推荐封装请求 hooks,把取消逻辑统一处理。
工程中搜索、筛选、自动补全这类高频请求特别需要处理竞态。
五、取消信号、超时与竞态要分开
AbortController 可以让多个任务共享一个 signal,也能把“用户取消”和“超时取消”统一成控制流。调用 abort 后,fetch Promise 拒绝,但响应体读取阶段也可能因信号中止而失败。
async function load(url, signal) {
try {
const response = await fetch(url, { signal })
if (!response.ok) throw new Error(`HTTP ${response.status}`)
return await response.json()
} catch (error) {
if (signal.aborted) return { cancelled: true, reason: signal.reason }
throw error
}
}
| 手段 | 浏览器是否停止等待 | 防旧结果覆盖 | 服务端是否必停 |
|---|---|---|---|
| abort | 尽力停止 | 是 | 不保证 |
| 请求序号 | 否 | 是 | 否 |
| 超时 Promise | 取决于是否联动 abort | 可能 | 否 |
| 服务端取消协议 | 视实现 | 是 | 可协作停止 |
取消是资源管理,竞态控制是状态正确性;可靠实现常把 abort 与“只接受最新请求”同时使用。
六、用搜索时间线验证最后写入
用户在 0ms 输入 a,100ms 输入 ab,200ms 输入 abc。三个接口分别在 900ms、500ms、300ms 后返回,则返回顺序可能是 abc → ab → a;若每次都 setState,最终页面反而显示最旧的 a。
请求编号:1(a) ─────────────900ms
2(ab) ─────500ms
3(abc) ─300ms ← 只有编号 3 可更新
每次新搜索先 abort 旧 controller,并记录 latestId;响应回来还要比较 id,覆盖极短窗口和无法真正取消的适配器。组件卸载时取消并清理 loading,取消不应弹“网络错误”。
写请求尤其不能把前端取消当回滚。订单创建可能已在服务器提交,即使浏览器断开也要通过幂等键、查询状态和事务确认最终结果。
七、常见误区与追问
- 误区:调用 abort 后服务器一定停止处理。 请求可能已经到达并提交,浏览器只能保证自己的等待和流处理被中止。
- 误区:只要取消旧请求就无需序号检查。 适配器可能不支持取消,或响应正处于完成竞态,最新编号仍是保险。
- 误区:AbortError 应和网络失败一样提示。 用户主动取消是预期控制流,通常不应弹错误。
- 追问:一个 signal 能否控制多个 fetch? 可以,abort 后所有监听该 signal 的未完成操作都会收到取消。
- 追问:请求超时如何实现? 定时触发 controller.abort,并在 finally 清定时器,同时区分超时与用户取消原因。
- 追问:为什么取消后还要恢复 loading? 网络停止不等于 UI 状态自动复原,清理逻辑要放 finally 或统一状态机。
- 追问:写请求取消后如何确认结果? 使用幂等键并查询服务端最终状态,不能仅凭客户端 Promise 拒绝判断失败。
八、加强记忆
- 触发场景:卸载、路由切换、搜索换词、上传下载取消。
- 标准工具:AbortController 产生 signal,调用 abort 广播取消。
- 错误分流:取消、超时、网络错误和 HTTP 错误分别建模。
- 竞态保险:abort 旧请求,同时只允许最新请求编号写 UI。
- 生命周期:清理 loading、进度、定时器和闭包引用。
- 服务端边界:前端取消不等于事务撤销,写操作仍靠幂等与状态查询。