AbortSignal:以前我没得选,现在我想中止promise

JavaScript/前端
348
0
0
2022-11-09
标签   Promise

大家好,我卡颂。

遥想数年前的一次面试,面试官问我:promise有什么缺点?

img

真是百思不得姐啊...

答案是:promise一旦初始化,就不能中止。这是由promise的实现决定的。

AbortSignal的出现使promise从语义上变为可中止的。并且,只要符合规范,所有异步操作都能变为「可中止的」

AbortSignal是什么

AbortSignal是个实验性API,不过兼容性还不错,而且polyfill实现起来也不复杂。

img

AbortSignal可以实例化一个「信号对象」signal object)。

AbortController可以实例化一个「信号对象」的控制器。

就像遥控器可以发出信号关电视一样,AbortController的实例可以控制中止信号。

img

只要符合AbortSignal的接入规范,任何异步操作都能实现中止功能。

举个例子,首先new一个控制器实例:

// 控制器实例
const controller = new AbortController();
const signal = controller.signal;

其中signal是控制器对应的「信号对象」

「信号对象」可以监听abort事件,当信号被中止时被触发。

调用controller.abort()方法后会中止信号,此时signal.abortedtrue

// 监听 abort 事件
signal.addEventListener('abort', () => {
  console.log("信号中止!")
});

// 控制器中止信号
controller.abort(); 

console.log('是否中止:', signal.aborted); 

如上代码调用后会依次打印:

  1. 信号中止!
  2. 是否中止:true

img

在fetch中的应用

fetch API已经集成了AbortSignal

只需要将controller内的「信号对象」作为signal参数传给fetch

const controller = new AbortController();
fetch(url, {
  signal: controller.signal
});

当调用controller.abort()后,fetchpromise会变为AbortError DOMException reject

fetch('xxxx', {
  signal: controller.signal
}).then(() => {}, err => {
  if (err.name == 'AbortError') { 
    // 中止信号
  } else {
    // 其他错误
  }
})

可以在此时处理中止后的操作。

这里有个取消视频下载Demo[1],可以看看fetch如何配合AbortSignal实现取消下载

与任何异步操作结合

不仅是fetch,任何异步操作只要符合如下规范,都可以与AbortError集成:

  1. AbortSignal(信号对象)作为APIsignal参数传入
  2. 约定如果API返回的promise变为AbortError DOMException reject则代表操作被中止
  3. 如果signal.aborted === true则立刻让promise变为reject
  4. 观测AbortSignal状态的变化

如果API应用场景比较复杂(比如需要考虑多线程通信),文档中提供了一套基于「订阅发布」abort-algorithms[2]机制来完成步骤4。

img

总结

虽然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