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

4,120 阅读2分钟

大家好,我卡颂。

遥想数年前的一次面试,面试官问我: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.abortedtrue

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

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

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

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

  1. 信号中止!

  2. 是否中止:true

在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,可以看看fetch如何配合AbortSignal实现取消下载

与任何异步操作结合

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

  1. AbortSignal(信号对象)作为APIsignal参数传入

  2. 约定如果API返回的promise变为AbortError DOMException reject则代表操作被中止

  3. 如果signal.aborted === true则立刻让promise变为reject

  4. 观测AbortSignal状态的变化

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

总结

虽然AbortSignal原理很简单,但只要遵守接入规范,他的可扩展性是很强的。

比如,可以将一个signal传给多个符合规范的API,就能用一个控制器中止多个API的调用。

就像一个遥控器,同时操作家里的空调、电视、洗衣机,你爱了么?

欢迎加入人类高质量前端框架研究群,带飞