AbortController
一个「取消信号」,可以传给
fetch、事件监听、流,让正在进行的异步操作停下来。
基本用法
js
const controller = new AbortController();
fetch("/api/list", { signal: controller.signal })
.then((res) => res.json())
.catch((err) => {
if (err.name === "AbortError") {
console.log("请求已取消");
return;
}
throw err;
});
// 需要取消时
controller.abort();abort() 之后,fetch 返回的 Promise 会以 AbortError 拒绝,signal.aborted 变成 true。
abort 是不可逆的:同一个 controller 不能复用,下一次请求要新建一个。
超时
以前要自己 setTimeout 再调 abort(),现在有静态方法:
js
const res = await fetch("/api/slow", {
signal: AbortSignal.timeout(5000),
});超时抛出的不是 AbortError,而是 TimeoutError,两者要分开处理:
js
try {
await fetch(url, { signal: AbortSignal.timeout(5000) });
} catch (err) {
if (err.name === "TimeoutError") {
// 超时
} else if (err.name === "AbortError") {
// 被主动取消
}
}AbortSignal.timeout() 是 2024 年才进入 Baseline 的,老浏览器可能没有。不支持的时候调用这个方法本身就会抛 TypeError,可以先判断:
js
const signal =
typeof AbortSignal.timeout === "function"
? AbortSignal.timeout(5000)
: undefined;
fetch(url, { signal });不支持就没有超时兜底,这一点要心里有数,别让请求静默地挂着。
组合多个信号
「用户点取消」和「5 秒超时」同时生效:
js
const controller = new AbortController();
const signal = AbortSignal.any([
controller.signal,
AbortSignal.timeout(5000),
]);
fetch(url, { signal });任意一个触发,组合信号就 abort,reason 取第一个触发的那个。
注意 AbortSignal.any() 没有「退订」方法:返回的信号会一直和输入的信号关联。别在长生命周期对象上反复组合,否则旧信号会一直挂着。
给事件监听也装上
addEventListener 的第三个参数支持 signal:
js
const controller = new AbortController();
window.addEventListener("scroll", onScroll, { signal: controller.signal });
window.addEventListener("resize", onResize, { signal: controller.signal });
// 一处取消,全部解绑
controller.abort();比保存一堆 handler 再逐个 removeEventListener 干净得多,SPA 组件卸载时尤其好用。
常见坑
- 每次请求新建 controller。复用一个已经 abort 的 controller,请求会立刻失败。
signal.reason不一定是Error。abort()可以传自定义原因,abort("用户离开")之后reason就是那个字符串;不传时才是DOMException(AbortError)。- 取消不等于服务端停止。请求可能已经发到后端,服务端还在继续执行;要真正中断需要业务层配合。
- Axios 新版本也支持
signal。老版本只有CancelToken,升级后建议统一用 signal,少一套 API。 - 流式响应也会被中断。
fetch的response.body读取过程中 abort,reader.read()会以AbortError拒绝。 - 在 Vue / React 里,卸载时
abort()是防止「组件已经卸载还在 setState」最省事的写法,配合onUnmounted/useEffect的 cleanup 使用。
