AbortController
是一个DOM API。MDN上对它的介绍是 AbortController接口表示一个控制器对象,允许根据需要终止一个或多个Web请求。
但是它只能用来终止请求吗,那当然不是?
目前笔者只发现的2个AbortController
的使用场景,分别是:fetch
和addEventListener
,下文分别介绍这两种情况。
fetch
fetch
提供了一个可选参数signal
,可以用来终止请求。
那signal是啥?是怎么终止的?表现形式是啥?真的取消请求了还是只是不接受返回的数据?
下面通过一个案例来实践一下:
页面上提供了2个按钮,分别用来fetch(请求)和abort(废弃请求)。然后通过给fetch
添加signal
来控制请求:
const controller = new AbortController(); // 新建一个AbortController实例
let signal = controller.signal; // signal是AbortController实例的属性
const downloadBtn = document.querySelector('.download');
const abortBtn = document.querySelector('.abort');
downloadBtn.addEventListener('click', fetchVideo);
abortBtn.addEventListener('click', function () {
controller.abort(); // 调用abort方法
console.log('Download aborted');
})
function fetchVideo() {
//...
fetch(url, {signal}).then(function(response) {
//...
}).catch(function(e) {
console.error('e', e);
})
}
点击下载按钮,请求发起,此时请求状态是 待处理(pending):
请求还在进行中时,点击废弃按钮(实际上是调用了AbortController实例的abort方法),状态变成 已取消(canceled):
取消后,fetch
请求的promise 将 reject 一个名为 AbortError 的 DOMException,也就是会被例子中的catch捕获到,可以在控制台看到如下错误:
需要注意的是已经abort的请求是不能重复调用的,看fetch源码的实现就可以知道为什么,AbortSignal 在 abort 之后,状态就变成了 aborted,那么下次调用 fetch 请求的时候,当读到 AbortSignal 的状态是 aborted 的时候,就直接 reject 。
// fetch仓库地址: https://github.com/github/fetch/blob/master/fetch.js
if (request.signal && request.signal.aborted) {
return reject(new DOMException('Aborted', 'AbortError'))
}
也就是说在业务上,如果有同一个请求发了多次只想保留最后一个的话,我们需要用多个controller来控制,每发送一个请求就把上次的请求abort。
addEventListener
AbortController还可以作为addEventListener第三个参数options上的可选属性:
按照mdn的说法,当调用abort()后,监听器会被移除。其实这就相当于给我们内置了一个移除监听器的方式,可以不用把callback单独提取出成一个函数。
测试
用一个测试按钮,添加监听器,然后在监听函数中使用setTimeout执行abort
在点击测试监听按钮前,可以在元素里找到监听器:
点击测试按钮,控制台输出'监听',同时自动移除元素的监听器(需要先关闭控制台重新开):
众所周知,如果需要 removeEventListenr(type, callback)
, 它的callback必须和addEventListener
是同一个函数引用,而在某些业务场景下,我们并不想多写函数可以改成用signal
来控制。
例如,当在按钮鼠标时设置一个监听器,在监听器中再监听鼠标移动,鼠标松开关闭监听器:
document.addEventListener('mousedown', callback);
document.addEventListener('mouseup', callback2);
function callback (e) {
document.addEventListener('mousemove', callback3);
}
function callback2 (e) {
document.removeEventListener('mousemove', callback3);
}
function callback3(event) {}
如果改写成用AbortController
怎么写呢?
const controller = new AbortController();
function callback (e) {
document.addEventListener('mousemove', (e) => {
},{
signal: controller.signal
});
}
document.addEventListener('mousedown', callback);
document.addEventListener('mouseup', controller.abort);
总结
AbortController
可以用在fetch和addEventListener,分别用来废弃请求和废弃监听器。