凌晨你盯着监控大盘:用户点击"取消"后,页面还在拼命转圈,后台接口却像失控的火车,带着 200 MB 的文件流在公网上狂奔。 这是我们迫切想中断这个请求,但 Promise 内部也没给我们通过中断请求的方法
这个时候不径想到了角落里那个不起眼的 AbortController
。
一个 3 行代码就能"真·取消"任何 fetch
/stream
/setTimeout
的小工具, 今天,我们就把它从冷宫里捞出来,看看它到底怎么把异步任务从"失控"变成"可控"。
1、AbortController核心思想
-
创建一个
AbortController
实例。 -
从
实例
获取一个signal
属性。 -
将这个
signal
传递给一个支持取消的异步操作。 -
当你想取消这个操作时,调用
实例
的abort()
方法。
1.1、实例 signal 属性
signal 是一个 AbortSignal 实例,可被用于任何支持中止的 API(如 fetch()、事件监听器等)
一旦 abort() 被调用,signal 就会触发 abort 事件,标记为已中止
1.2、实例 abort() 方法
- 调用后会让该 signal 上监听的所有异步操作同时中止
js
const controller = new AbortController();
window.addEventListener('resize', () => {
console.log('Window resized!');
}, { signal: controller.signal });
// 调用 abort(),会自动移除 resize 监听器
document.getElementById('but').onclick = () => {
controller.abort();
}
2、AbortController能中止哪些操作?
Promise
(通过手动检查信号状态)fetch()
请求XMLHttpRequest
(XHR) 请求EventSource
连接WebSocket
连接 (部分实现)FileReader
操作Image
加载- Js事件
2.1、fetch中断
fetch原生支持AbortController事件
js
let controller = new AbortController();
fetch(url, {
signal: controller.signal
}).catch((err)=>
if (error.name === 'AbortError') {
console.log('Abort中断');
});
setTimeout(() => controller.abort(), 1000);//中断
-
我们创建了一个
controller
和它的获取signal
。 -
fetch
请求的signal
选项被设置为controller.signal
。 -
当
controller.abort()
被调用时,signal
会触发abort
事件,fetch
请求会立即终止,并抛出一个AbortError
。 -
在
catch
块中,我们可以通过error.name === 'AbortError'
来区分是中止错误还是其他网络错误。
2.1.1、批量 fetch 中断
js
let urls = [...]; // 要并行 fetch 的 url 列表
let controller = new AbortController();
// 一个 fetch promise 的数组
let fetchJobs = urls.map(url => fetch(url, {
signal: controller.signal
}));
let results = await Promise.all(fetchJobs);
controller.abort()
// 它都将中止所有 fetch
2.2、中断Promise
当调用abort时,将在Promise内部改变Promise状态,达到中断Promise的目标
js
// 1. 一个需要 5 秒才能完成的异步任务
function slowTask(signal) {
return new Promise((resolve, reject) => {
// 如果信号已经 abort,立即退出
if (signal.aborted) {
return reject(new Error('Aborted before start'));
}
const timer = setTimeout(() => {
resolve('任务完成');
}, 5000);
// 监听 abort 事件
signal.addEventListener('abort', () => {
clearTimeout(timer); // 清理资源
reject(new Error('Aborted by user')); // 主动让 Promise rejected
});
});
}
// 2. 使用 AbortController
const controller = new AbortController();
const signal = controller.signal;
// 3. 启动任务
slowTask(signal)
.then((res)=>consloe.log(res))
// 4. 2 秒后手动中断
setTimeout(() => {
console.log('手动 abort');
controller.abort();
}, 2000);
2.3、中断xhr
2.3.1、原生中断
xhr原生就支持中断,内部就存在一个
abort函数
可进行中断
js
const xhr = new XMLHttpRequest();
const method = 'GET';
const url = 'https://xxx';
xhr.open(method, url, true);
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
}
}
xhr.send();
setTimeout(()=>{
xhr.abort()}
,1000)
2.3.2、abortController中断
js
const controller = new AbortController();
const signal = controller.signal;
signal.addEventListener('abort', () => {
console.log('XHR request aborted!');
});
function loadXHRData() {
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://jsonplaceholder.typicode.com/todos/1');
// 将 signal 绑定到 XHR
xhr.signal = signal; // 注意:这是较新的 XHR 规范,旧浏览器可能不支持
xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) {
console.log('XHR data received:', JSON.parse(xhr.responseText));
} else {
console.error('XHR error:', xhr.status, xhr.statusText);
}
};
xhr.send();
}
loadXHRData();
setTimeout(() => {
console.log('Attempting to abort XHR request...');
controller.abort();
}, 100);
2.4、中断js事件
AbortController也能控制Js事件,包括dom事件。
在注册事件时,可以控制signal将直接将事件取消掉
js
const controller = new AbortController();
window.addEventListener('resize', () => {
console.log('Window resized!');
}, { signal: controller.signal });
// 调用 abort(),会自动移除 resize 监听器
document.getElementById('but').onclick = () => {
controller.abort();
}
三、注意事项
- 及时清理资源
当请求被取消后,确保及时清理与请求相关的资源,避免内存泄漏或其他潜在问题。 - 错误处理
在处理fetch
请求的Promise链时,要特别注意AbortError
的处理。确保能够区分是因取消请求而引发的错误还是其他类型的错误,以便进行正确的错误处理。 - 多次调用abort
abort
方法可以被多次调用,但第二次及以后的调用不会有任何效果。一旦请求被取消,它将保持取消状态。 - 与其他API的兼容性
虽然AbortController
在现代浏览器中的支持已经相当广泛,但在一些较老的浏览器版本中可能还不支持。因此,在使用AbortController
时,要注意检查目标浏览器的兼容性情况,并考虑使用Polyfill或备选方案来确保功能的可用性。 - 不要滥用
虽然AbortController
提供了取消请求的能力,但并不意味着我们应该滥用它。频繁地取消和重新发起请求可能会对服务器造成不必要的负担,也可能影响用户体验。因此,在使用AbortController
时,要谨慎考虑是否真的需要取消请求,并尽量避免不必要的取消操作。