王一之 发表于 2022-9-15 09:56:12

【分享】如何优雅地中断 Promise?来试试 AbortController 吧!

早上摸鱼时发现的,头次知道(https://developer.mozilla.org/zh-CN/docs/Web/API/AbortController#%E6%B5%8F%E8%A7%88%E5%99%A8%E5%85%BC%E5%AE%B9%E6%80%A7),可以中断Promise,觉得不错,分享一下

浏览器兼容性也可以,考虑后续脚本猫后台脚本的中断就采用这个方案


原文:https://mp.weixin.qq.com/s/liTMB0sTcl6jkEjUPzCUjA

李恒道 发表于 2022-9-15 10:03:49

这是中断fetch的吧

王一之 发表于 2022-9-15 10:07:50

李恒道 发表于 2022-9-15 10:03
这是中断fetch的吧

Promise也可以,只是得手动实现?更多应该是一个规范,用它提供的接口去判断

```js
/**
* 自定义的可以主动取消的 Promise
*/

function myCoolPromise ({ signal }) {
    return new Promise((resolve, reject) => {
      // 如果刚开始 signal 存在并且是终止的状态可以直接抛出异常
      signal?.throwIfAborted();

      // 异步的操作,这里使用 setTimeout 模拟
      setTimeout(() => {
            Math.random() > 0.5 ? resolve('ok') : reject(new Error('not good'));
      }, 1000);

      // 添加 abort 事件监听,一旦 signal 状态改变就将 Promise 的状态改变为 rejected
      signal?.addEventListener('abort', () => reject(signal?.reason));
    });
}

/**
* 使用自定义可取消的 Promise
*/

const ac = new AbortController();
const { signal } = ac;

myCoolPromise({ signal }).then((res) => console.log(res), err => console.warn(err));
setTimeout(() => {
    ac.abort();
}, 100); // 可以更改时间看不同的结果
```

cxxjackie 发表于 2022-9-15 12:06:14

AbortController中断Promise的效果其实没有那么理想,他只是提前resolve/reject,如果不做任何处理,Promise实际还是在运行的:
function myPromise(signal) { // 吐槽下示例的写法,额外创建了一个对象,还不如把ac传进来算了
    return new Promise(resolve => {
      let i = 0;
      const timer = setInterval(() => {
            console.log(++i);
            if (i === 10) {
                clearInterval(timer);
                resolve('done');
            }
      }, 500);
      signal.addEventListener('abort', () => resolve('aborted'));
    });
}
const ac = new AbortController();
const { signal } = ac;
myPromise(signal).then(e => console.log(e));
setTimeout(() => {
    ac.abort();
}, 2000);
这个例子可以看到,中断后定时器还是在继续执行,这与Promise的特性有关,要正确中断必须自己加clearInterval才行。AbortController只是抛出一个中断信号,具体的中断实现和收尾工作还得自己写,换个角度说,即使不用AbortController,这种“中断”特性也可以轻松实现:
function myPromise(signal) {
    return new Promise(resolve => {
      let i = 0;
      const timer = setInterval(() => {
            console.log(++i);
            if (i === 10) {
                clearInterval(timer);
                resolve('done');
            }
      }, 500);
      signal.abort = () => {
            clearInterval(timer);
            resolve('aborted');
      }
    });
}
const signal = {};
myPromise(signal).then(e => console.log(e));
setTimeout(() => {
    signal.abort();
}, 2000);
至于中断fetch和addEventListener,实际也是因为这些API内部实现了中断逻辑(有一定兼容性问题)。中断Promise听上去很高大上,其实也就那回事,就是同时中断多个比较方便而已。

王一之 发表于 2022-9-15 13:38:18

cxxjackie 发表于 2022-9-15 12:06
AbortController中断Promise的效果其实没有那么理想,他只是提前resolve/reject,如果不做任何处理,Promis ...

是的,还是得自己手动处理

优点就是这是规范,很多地方都有用

cxxjackie 发表于 2022-9-15 21:22:25

试着手写了一下AbortController的实现,感觉这玩意是真的简单:
class AbortSignal extends EventTarget {
    constructor() {
      super();
      this.aborted = false;
      this.reason = undefined;
      this.onabort = null;
    }
    throwIfAborted() {
      if (this.aborted) throw this.reason;
    }
}
class AbortController {
    constructor() {
      this.signal = new AbortSignal();
    }
    abort(reason = new DOMException('signal is aborted without reason')) {
      this.signal.aborted = true;
      this.signal.reason = reason;
      const evt = new Event('abort');
      this.signal.dispatchEvent(evt);
      if (typeof this.signal.onabort === 'function') this.signal.onabort(evt);
    }
}

王一之 发表于 2022-9-15 21:25:28

cxxjackie 发表于 2022-9-15 21:22
试着手写了一下AbortController的实现,感觉这玩意是真的简单:

话说这手写的,fetch能用么?

cxxjackie 发表于 2022-9-15 22:03:38

王一之 发表于 2022-9-15 21:25
话说这手写的,fetch能用么?

不行,过不了类型判断,fetch应该有真正的AbortSignal引用。这个AbortSignal目前应该算实验性质的接口,没有提供实例化方法(只能实例化一个已中断的signal),所以我才重新写了一个,以后可能可以直接new吧。
页: [1]
查看完整版本: 【分享】如何优雅地中断 Promise?来试试 AbortController 吧!