⭐ 推荐:社区规则条款 V1.0

用AbortController取消异步请求的正确姿势,你掌握了吗?

陈先生
陈先生 正式会员正式会员认证极客认证极客
发布于 2026-08-14 14:10 ·98 浏览 ·0 回复
内容摘要

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()。

常见坑,踩过的人不少

  1. 复用同一个 signal 给多个请求,一次 abort 会全部取消。每个请求应该有自己的 controller。
  2. abort 后继续用同一个 signal 发请求,会立即取消。必须新建。
  3. 在 axios 中,从 v0.22 开始支持 signal,直接在 config 里传 signal 即可。旧版 cancelToken 已经废弃,建议统一用 signal。
  4. abort 不保证请求没发出去,只是不再处理响应。服务端可能仍然收到请求,所以取消不等于“回滚”。
  5. 取消后别忘了把 loading 状态置为 false,否则界面可能一直转圈。

总结

AbortController 的 API 很简单,难的是养成“请求与生命周期绑定”的意识。取消不是异常,而是正常的控制流。把 AbortError 单独处理,在组件卸载、依赖变化、新请求覆盖旧请求时主动 abort,代码会健壮很多。下次写异步请求时,先问自己一句:这个请求需要能被取消吗?如果答案是肯定的,AbortController 就该出场了。

本文转载自 Clara轻量论坛系统,原文地址:https://www.leleweb.cn/thread-221.html
转载请注明出处,版权归原作者所有。
—/ 5暂无评分
还没有评分,来打第一分吧

全部回复 0

还没有回复,来抢沙发~