用AbortController取消异步请求的正确姿势,你掌握了吗?
AbortController 通过将 signal 传给 fetch、axios 等异步请求,并在组件卸载、依赖变化或发生竞态时调用 abort 取消旧请求,同时把 AbortError 视为预期取消而静默处理、仅上报真实异常,从而避免旧响应覆盖新结果和卸载后更新状态,比 isMounted 标志位更优雅。
你有没有遇到过这种情况:搜索框里刚敲完关键词,请求发出去了,用户又改了输入,结果旧请求的响应后到,直接把新结果覆盖了。或者组件已经卸载,请求才慢悠悠回来,控制台弹出一句 Can't perform a React state update on an unmounted component。很多人第一反应是加个 isMounted 标志位,但原生 AbortController 才是更优雅的解法。
AbortController 并不复杂,真正难的是把它用对地方、用对姿势。下面我们从基础用法说到几个高频场景,看看你是不是真的掌握了。
基础用法:一个最小示例
AbortController 提供一个 signal 属性和一个 abort() 方法。把 signal 传给 fetch、axios 等支持它的异步操作,调用 abort() 就能取消。
const controller = new AbortController();
fetch('/api/search?q=' + keyword, { signal: controller.signal })
.then(res => res.json())
.then(data => render(data))
.catch(err => {
if (err.name === 'AbortError') return;
console.error(err);
});
// 需要取消时
controller.abort();
注意:abort() 之后,这个 signal 就进入 aborted 状态,不可重置。同一个 controller 不能拿来发第二个请求,必须新建。
正确姿势一:区分“取消”和“真实异常”
很多人把 catch 里所有错误都上报监控,结果取消也被算成了错误。正确做法是判断 err.name === 'AbortError' 或 err.code === 20。对于 fetch,取消会 reject 一个 AbortError;如果不处理,控制台会看到未捕获的 Promise rejection。
取消是预期行为,不该打扰用户,也不该污染错误监控。所以:
.catch(err => {
if (err.name === 'AbortError') return; // 静默处理
reportError(err); // 只有真实异常才上报
});
正确姿势二:组件卸载和依赖变化时取消
React 的 useEffect 里最典型:
useEffect(() => {
const controller = new AbortController();
fetch(url, { signal: controller.signal })
.then(res => res.json())
.then(setData)
.catch(err => {
if (err.name !== 'AbortError') throw err;
});
return () => controller.abort();
}, [url]);
Vue 的 onUnmounted 同理。这样既避免了内存泄漏,也避免了无效的状态更新。
正确姿势三:竞态请求只保留最后一次
搜索联想场景,每次输入都发请求,必须取消上一个。可以用一个变量保存 controller:
let controller;
async function search(keyword) {
controller?.abort();
controller = new AbortController();
try {
const res = await fetch(`/api/search?q=${keyword}`, {
signal: controller.signal
});
const data = await res.json();
render(data);
} catch (err) {
if (err.name !== 'AbortError') throw err;
}
}
这样只有最后一次请求能真正渲染,前面被取消的请求不会污染 UI。
正确姿势四:超时与组合信号
AbortSignal.timeout(5000) 可以创建一个超时信号:
fetch(url, { signal: AbortSignal.timeout(5000) });
如果需要同时支持“用户手动取消”和“超时自动取消”,可以用 AbortSignal.any([controller.signal, AbortSignal.timeout(5000)]),不过这个 API 较新,注意兼容性。或者自己封装一个 setTimeout 调用 abort()。
常见坑,踩过的人不少
- 复用同一个
signal给多个请求,一次abort会全部取消。每个请求应该有自己的 controller。 abort后继续用同一个signal发请求,会立即取消。必须新建。- 在
axios中,从 v0.22 开始支持signal,直接在 config 里传signal即可。旧版cancelToken已经废弃,建议统一用signal。 abort不保证请求没发出去,只是不再处理响应。服务端可能仍然收到请求,所以取消不等于“回滚”。- 取消后别忘了把 loading 状态置为 false,否则界面可能一直转圈。
总结
AbortController 的 API 很简单,难的是养成“请求与生命周期绑定”的意识。取消不是异常,而是正常的控制流。把 AbortError 单独处理,在组件卸载、依赖变化、新请求覆盖旧请求时主动 abort,代码会健壮很多。下次写异步请求时,先问自己一句:这个请求需要能被取消吗?如果答案是肯定的,AbortController 就该出场了。
转载请注明出处,版权归原作者所有。
正式会员
认证极客






