Skip to content

AbortController

AbortController 用于取消异步操作,最常见的场景是取消 fetch 请求,也可以和其他支持 AbortSignal 的 Web API 配合使用。

基本概念

它主要包含两部分:

  • AbortController:负责发出取消信号
  • AbortSignal:传给目标 API 的信号对象
js
const controller = new AbortController()
const { signal } = controller

最常见场景:取消 fetch

js
const controller = new AbortController()

fetch('/api/users', {
  signal: controller.signal,
})
  .then((response) => response.json())
  .then((data) => {
    console.log(data)
  })
  .catch((error) => {
    if (error.name === 'AbortError') {
      console.log('请求已取消')
      return
    }

    console.error(error)
  })

controller.abort()

调用 abort() 后,请求通常会以 AbortError 结束。

为什么要主动取消请求

很多时候不是“请求发错了”,而是“这个请求已经没有继续存在的价值”。

典型情况包括:

  • 页面已经切走
  • 新请求已经替代旧请求
  • 到了超时时间

典型场景

组件卸载时取消请求

避免页面切换后无意义的请求继续执行。

js
const controller = new AbortController()

fetch('/api/detail', { signal: controller.signal })

window.addEventListener('beforeunload', () => {
  controller.abort()
})

搜索联想只保留最后一次请求

js
let controller

async function search(keyword) {
  controller?.abort()
  controller = new AbortController()

  const response = await fetch(`/api/search?q=${encodeURIComponent(keyword)}`, {
    signal: controller.signal,
  })

  return response.json()
}

这类场景里,取消旧请求通常比“等旧请求回来再丢弃结果”更直接。

超时控制

fetch 本身没有原生 timeout 配置,所以很常见的一种做法就是配合 AbortController 自己封装:

js
async function fetchWithTimeout(url, timeout = 5000) {
  const controller = new AbortController()
  const timer = setTimeout(() => controller.abort(), timeout)

  try {
    return await fetch(url, {
      signal: controller.signal,
    })
  } finally {
    clearTimeout(timer)
  }
}

这也是它在项目里最常见的价值之一。

和事件监听配合

部分浏览器 API 支持把 signal 传给 addEventListener,取消时可自动移除监听器。

js
const controller = new AbortController()

window.addEventListener(
  'resize',
  () => {
    console.log('resize')
  },
  { signal: controller.signal },
)

controller.abort()

这表示同一个取消信号不只可以控制请求,也可以控制部分事件监听生命周期。

使用时要注意什么

AbortError 不是普通业务失败

js
try {
  await fetch('/api/users', { signal: controller.signal })
} catch (error) {
  if (error.name === 'AbortError') {
    return
  }

  throw error
}

如果不单独区分,日志里很容易把“主动取消”误判成“接口报错”。

取消不等于回滚服务端操作

abort() 更多是终止浏览器侧这条异步流程,并不一定意味着服务端已经完全停止处理。

使用建议

  • 请求存在竞态时,优先考虑取消旧请求,而不是只在结果返回后再判断是否丢弃。
  • 处理异常时,单独识别 AbortError,不要把它当成真正的接口失败。
  • 如果只是做超时控制,也可以基于 AbortController 自己封装超时取消。

基于 MIT 许可发布