前端开发者常常需要处理“取消任务”的场景:接口请求发出后用户快速切换页面、组件卸载、搜索防抖中断上一次请求等等。这种需求本质上就是**“任务中断”**。
但在过去,JavaScript 没有原生取消异步任务的能力。我们通常要么靠手动标记变量、要么写一些不太优雅的 hack 逻辑。但现在有了 AbortController,一切变得清爽、现代、标准化。
这篇文章将带你系统掌握 AbortController,包括:
- 它究竟能做什么?
- 如何优雅地用它终止请求?
- 实际应用中的注意事项与踩坑点
- 它还能“终止”谁?
为什么我们需要 AbortController?
先抛个现实问题:假如你在搜索框中绑定了一个 fetch 请求,每次输入都会触发新的请求。
input.addEventListener('input', (e) => {
fetch(`/search?q=${e.target.value}`)
.then(res => res.json())
.then(data => updateResults(data))
})
用户快速输入,连续触发了五六个请求,但服务器可能按顺序返回。这就出现了经典问题:
后发请求先返回,覆盖了正确的结果。
为了避免这个问题,你需要中止前一个请求。过去我们要靠自己管理“状态标记”或是 Axios 的取消令牌(现在也废弃了)。而现在,AbortController 原生就能干这事。
AbortController 基本用法
const controller = new AbortController()
const signal = controller.signal
fetch('/api/data', { signal })
.then(res => res.json())
.then(data => console.log(data))
.catch(err => {
if (err.name === 'AbortError') {
console.log('请求被取消了')
} else {
console.error('其他错误', err)
}
})
// 某个时机触发取消
controller.abort()
你可以把这个逻辑想象成:
把任务和一个“遥控器”绑在一起,某个时刻按下“停止”按钮,就可以中止它。
AbortController 应用场景全集
✅ 1. 取消多次搜索请求(防抖、节流场景)
let controller = null
function search(keyword) {
if (controller) controller.abort() // 中断上一次
controller = new AbortController()
fetch(`/search?q=${keyword}`, { signal: controller.signal })
.then(res => res.json())
.then(updateUI)
.catch(err => {
if (err.name !== 'AbortError') console.error(err)
})
}
注意:
controller每次都重新生成,前一个就自然失效。这是处理用户快速输入时的黄金搭配。
✅ 2. 组件卸载时中止异步任务(React/Vue)
useEffect(() => {
const controller = new AbortController()
fetch('/api/data', { signal: controller.signal })
.then(...)
.catch(...)
return () => {
controller.abort()
}
}, [])
React 中常用在 useEffect 的清理阶段,避免组件卸载后仍处理异步结果(比如 setState 报错)。
Vue 中也可以结合 onUnmounted 实现类似效果。
✅ 3. 并发任务中断所有子任务(超时处理)
const controller = new AbortController()
const timeout = setTimeout(() => {
controller.abort()
}, 5000)
Promise.all([
fetch('/a', { signal: controller.signal }),
fetch('/b', { signal: controller.signal })
]).then(...).catch(...)
这种写法可以让你设置一个总超时时间,一旦超时就同时中断多个请求。
✅ 4. 自定义异步任务支持取消
不仅仅是 fetch,你还可以让你自定义的异步任务也响应 AbortController。
function wait(ms, signal) {
return new Promise((resolve, reject) => {
const id = setTimeout(resolve, ms)
signal?.addEventListener('abort', () => {
clearTimeout(id)
reject(new DOMException('Aborted', 'AbortError'))
})
})
}
const controller = new AbortController()
wait(3000, controller.signal).then(() => {
console.log('Done')
}).catch(err => {
if (err.name === 'AbortError') console.log('任务中断')
})
只要你自己监听
abort事件,你就能让任何异步任务“听话地终止”。
冷知识:AbortController 不止用于 fetch
大部分人只用它和 fetch 配合,但其实 任何支持 signal 的 API 都能使用,比如:
ReadableStreamWebSocket(部分 polyfill 支持)- 自定义异步任务(如上)
- 一些 Web Worker 协议中也逐步加入支持
随着 Web API 的不断完善,未来会有更多异步 API 原生支持 AbortSignal。
实战技巧与常见坑
☠️ 1. AbortController 不是“可重用”的!
它只能用一次。调用 .abort() 之后,它就“废了”。你需要为每次任务生成一个新的 controller。
☠️ 2. 没传 signal?那就是无效取消
你一定要把 controller.signal 显式传给任务,否则调用 .abort() 是无效的。
☠️ 3. 捕获错误时必须判断 AbortError
.catch(err => {
if (err.name === 'AbortError') {
// 被取消的,不是 bug
} else {
throw err // 真正的异常应该继续抛出
}
})
否则你会误把取消当作程序异常处理。
如何优雅封装一个支持中断的异步请求?
假设你有一个搜索请求模块,可以封装成这样:
function createAbortableSearch() {
let controller = null
return function search(keyword) {
if (controller) controller.abort()
controller = new AbortController()
return fetch(`/search?q=${keyword}`, { signal: controller.signal })
}
}
const search = createAbortableSearch()
search('vue')
search('vue3')
search('vue3 composition') // 只会保留最后一次请求
这种写法非常适合抽象在业务组件之外,让组件“只负责输入”,而不关心取消细节。
结语:AbortController 是现代前端的必修课
在现代 Web 中,异步操作越来越多、任务流越来越复杂。任务取消不再是“边角料功能”,而是现代 Web 应用“响应性”和“健壮性”的关键。
AbortController 带来了标准化的取消机制,优雅、清晰、可组合。掌握它,你就能:
- 写出更优雅的异步代码
- 更好地控制资源与生命周期
- 减少无效请求与副作用