取消请求最佳方案AbortController与传统axios的cancelToken

AbortController 取代 axios CancelToken:知识概要

一、为什么替代

CancelToken(axios 私有) AbortController(W3C 标准)
归属 axios 自家 API,v0.22 起标记 deprecated 浏览器标准,Node 18+ 原生
适用 仅 axios 请求 fetch、axios、事件监听、流,通用
超时 手写 setTimeout + cancel() AbortSignal.timeout(ms)
共享 一个 token 可共享但不直观 同一 signal 天然共享
趋势 逐步移除 新库统一支持

二、核心 API

const controller = new AbortController()

controller.signal              // 传给请求 / 监听
controller.signal.aborted      // 是否已中止
controller.signal.reason       // 中止原因
controller.abort()             // 触发中止,可传原因
controller.signal.addEventListener('abort', fn)
// 静态工厂
AbortSignal.timeout(5000)              // N 毫秒后自动中止
AbortSignal.any([a, b])                // 任一中止即中止(较新浏览器)

三、写法对照(1:1 迁移)

// 旧
const source = axios.CancelToken.source()
axios.post(url, data, { cancelToken: source.token })
source.cancel('用户离开')

// 新
const ac = new AbortController()
axios.post(url, data, { signal: ac.signal })
ac.abort('用户离开')

// fetch
fetch(url, { signal: ac.signal })

// 事件监听(一次性移除)
window.addEventListener('resize', h, { signal: ac.signal })
ac.abort()   // 请求 + 监听一起清理

四、错误识别

场景 表现
fetch DOMException,err.name === 'AbortError'
axios v1 抛 CanceledError,err.code === 'ERR_CANCELED',axios.isCancel(err) === true
const isAbortError = (err: any) =>
  err?.name === 'AbortError' ||
  err?.code === 'ERR_CANCELED' ||
  axios.isCancel?.(err)

五、典型场景

// 1. 取消上一个(防抖/竞态)
let ac: AbortController | null = null
async function search(kw: string) {
  ac?.abort('重复请求')
  ac = new AbortController()
  const r = await fetch(`/s?q=${kw}`, { signal: ac.signal })
}

// 2. 组件卸载取消
onBeforeUnmount(() => ac?.abort('unmount'))

// 3. 超时
fetch(url, { signal: AbortSignal.timeout(5000) })

// 4. 超时 + 手动取消
fetch(url, { signal: AbortSignal.any([ac.signal, AbortSignal.timeout(5000)]) })

// 5. 一个 signal 管多个请求
p1(a, { signal: ac.signal }); p2(b, { signal: ac.signal }); ac.abort()

六、通用封装范式

function createAbortable() {
  let controller: AbortController | null = null
  const run = <T>(fn: (signal: AbortSignal) => Promise<T>) => {
    controller?.abort('重复请求')
    controller = new AbortController()
    return fn(controller.signal)
  }
  return { run, abort: () => controller?.abort() }
}

七、易踩的坑(清单)

  1. 取消 ≠ 失败:abort 后 Promise 会 reject,必须用 isAbortError 过滤,否则误报"请求失败"。
  2. signal 一次性:abort 后不可复用,每次重新 new。
  3. 已中止的 signal 再发请求会立即失败。
  4. 不要混用:同一请求同时传 cancelToken 与 signal 易出诡异行为。
  5. 服务端无感知:前端中止只断开连接,后端可能已执行完;写操作不能靠它"回滚"。
  6. 清理要兜底:取消也会走 finally,自定义 loading / 状态要在 catch 中复位。
  7. axios 版本:signal 需 axios ≥ 0.22,老版本只能继续用 CancelToken。
  8. 原因传递:abort(reason) 支持任意值;约定用 new DOMException(msg, 'AbortError') 更规范。
posted @ 2026-10-13 15:20  寅信  阅读(3)  评论(0)    收藏  举报