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() }
}
七、易踩的坑(清单)
- 取消 ≠ 失败:abort 后 Promise 会 reject,必须用
isAbortError 过滤,否则误报"请求失败"。
- signal 一次性:abort 后不可复用,每次重新
new。
- 已中止的 signal 再发请求会立即失败。
- 不要混用:同一请求同时传
cancelToken 与 signal 易出诡异行为。
- 服务端无感知:前端中止只断开连接,后端可能已执行完;写操作不能靠它"回滚"。
- 清理要兜底:取消也会走
finally,自定义 loading / 状态要在 catch 中复位。
- axios 版本:
signal 需 axios ≥ 0.22,老版本只能继续用 CancelToken。
- 原因传递:
abort(reason) 支持任意值;约定用 new DOMException(msg, 'AbortError') 更规范。