大家好,我卡颂。
遥想数年前的一次面试,面试官问我:promise
有什么缺点?
真是百思不得姐啊...
答案是:promise
一旦初始化,就不能中止。这是由promise
的实现决定的。
AbortSignal
的出现使promise
从语义上变为可中止的。并且,只要符合规范,所有异步操作都能变为「可中止的」。
AbortSignal是什么
AbortSignal
是个实验性API
,不过兼容性还不错,而且polyfill
实现起来也不复杂。
AbortSignal
可以实例化一个「信号对象」(signal object
)。
AbortController
可以实例化一个「信号对象」的控制器。
就像遥控器可以发出信号关电视一样,AbortController
的实例可以控制中止信号。
只要符合AbortSignal
的接入规范,任何异步操作都能实现中止功能。
举个例子,首先new
一个控制器实例:
// 控制器实例
const controller = new AbortController();
const signal = controller.signal;
其中signal
是控制器对应的「信号对象」。
「信号对象」可以监听abort
事件,当信号被中止时被触发。
调用controller.abort()
方法后会中止信号,此时signal.aborted
为true
。
// 监听 abort 事件
signal.addEventListener('abort', () => {
console.log("信号中止!")
});
// 控制器中止信号
controller.abort();
console.log('是否中止:', signal.aborted);
如上代码调用后会依次打印:
- 信号中止!
- 是否中止:true
在fetch中的应用
fetch API
已经集成了AbortSignal
。
只需要将controller
内的「信号对象」作为signal
参数传给fetch
:
const controller = new AbortController();
fetch(url, {
signal: controller.signal
});
当调用controller.abort()
后,fetch
的promise
会变为AbortError DOMException reject
:
fetch('xxxx', {
signal: controller.signal
}).then(() => {}, err => {
if (err.name == 'AbortError') {
// 中止信号
} else {
// 其他错误
}
})
可以在此时处理中止后的操作。
这里有个取消视频下载Demo[1],可以看看fetch
如何配合AbortSignal
实现取消下载
与任何异步操作结合
不仅是fetch
,任何异步操作只要符合如下规范,都可以与AbortError
集成:
- 将
AbortSignal
(信号对象)作为API
的signal
参数传入 - 约定如果
API
返回的promise
变为AbortError DOMException reject
则代表操作被中止 - 如果
signal.aborted === true
则立刻让promise
变为reject
- 观测
AbortSignal
状态的变化
如果API
应用场景比较复杂(比如需要考虑多线程通信),文档中提供了一套基于「订阅发布」的abort-algorithms[2]机制来完成步骤4。
总结
虽然AbortSignal
原理很简单,但只要遵守接入规范,他的可扩展性是很强的。
比如,可以将一个signal
传给多个符合规范的API
,就能用一个控制器中止多个API
的调用。
就像一个遥控器,同时操作家里的空调、电视、洗衣机,你爱了么?
参考资料
[1] 取消视频下载Demo:
https://mdn.github.io/dom-examples/abort-api/
[2] abort-algorithms:
https://dom.spec.whatwg.org/#abortsignal-abort-algorithms