出处:掘金
原作者:ErpanOmer
前端开发者常常需要处理“取消任务”的场景:接口请求发出后用户快速切换页面、组件卸载、搜索防抖中断上一次请求等。这种需求本质上就是“任务中断”
但在过去,JavaScript 没有原生取消异步任务的能力。我们通常要么靠手动标记变量、要么写一些不太优雅的 hack 逻辑。但现在有了 AbortController,一切变得清爽、现代、标准化
这篇文章将带你系统掌握 AbortController,包括:
先抛个现实问题:假如你在搜索框中绑定了一个 fetch 请求,每次输入都会触发新的请求
input.addEventListener('input', (e) => {
fetch(`/search?q=${e.target.value}`)
.then(res => res.json())
.then(data => updateResults(data))
})
用户快速输入,连续触发了五六个请求,但服务器可能按顺序返回。这就出现了经典问题:后发请求先返回,覆盖了正确的结果
为了避免这个问题,你需要中止前一个请求。过去我们要靠自己管理“状态标记”或是 Axios 的取消令牌(现在也废弃了)。而现在,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()
你可以把这个逻辑想象成:把任务和一个“遥控器”绑在一起,某个时刻按下“停止”按钮,就可以中止它
假设你有一个搜索请求模块,可以封装成这样:
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') // 只会保留最后一次请求
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 每次都重新生成,前一个就自然失效。这是处理用户快速输入时的黄金搭配
useEffect(() => {
const controller = new AbortController()
fetch('/api/data', { signal: controller.signal })
.then(...)
.catch(...)
return () => {
controller.abort()
}
}, [])
React 中常用在 useEffect 的清理阶段,避免组件卸载后仍处理异步结果(比如 setState 报错)
Vue 中也可以结合 onUnmounted 实现类似效果
const controller = new AbortController()
const timeout = setTimeout(() => {
controller.abort()
}, 5000)
Promise.all([
fetch('/a', { signal: controller.signal }),
fetch('/b', { signal: controller.signal })
]).then(...).catch(...)
这种写法可以让你设置一个总超时时间,一旦超时就同时中断多个请求
不仅仅是 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 事件,就能让任何异步任务“听话地终止”
大部分人只用它和 fetch 配合,但其实 任何支持 signal 的 API 都能使用,比如:
ReadableStreamWebSocket(部分 polyfill 支持)随着 Web API 的不断完善,未来会有更多异步 API 原生支持 AbortSignal
AbortController 不是“可重用”的它只能用一次。调用 .abort() 之后,它就“废了”。你需要为每次任务生成一个新的 controller
signal?那就是无效取消你一定要把 controller.signal 显式传给任务,否则调用 .abort() 是无效的
AbortError.catch(err => {
if (err.name === 'AbortError') {
// 被取消的,不是 bug
} else {
throw err // 真正的异常应该继续抛出
}
})
否则会误把取消当作程序异常处理
在现代 Web 中,异步操作越来越多、任务流越来越复杂。任务取消不再是“边角料功能”,而是现代 Web 应用“响应性”和“健壮性”的关键
AbortController 带来了标准化的取消机制,优雅、清晰、可组合。掌握它,你就能: