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

阿乐
阿乐 管理员 黑卡会员
发布于 2026-09-11 13:39 ·3 浏览 ·0 回复

你有没有遇到过这种情况:搜索框里刚敲完关键词,请求发出去了,用户又改了输入,结果旧请求的响应后到,直接把新结果覆盖了。或者组件已经卸载,请求才慢悠悠回来,控制台弹出一句 `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` 就该出场了。

本文转载自 阿乐技术社区,原文地址:https://www.leleweb.cn/thread-221.html
转载请注明出处,版权归原作者所有。

全部回复 0

还没有回复,来抢沙发~