上一主题 下一主题
ScriptCat,新一代的脚本管理器脚本站,与全世界分享你的用户脚本油猴脚本开发指南教程目录
返回列表 发新帖

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

[复制链接]
  • TA的每日心情
    开心
    昨天 13:37
  • 签到天数: 213 天

    [LV.7]常住居民III

    305

    主题

    4193

    回帖

    4059

    积分

    管理员

    积分
    4059

    管理员荣誉开发者油中2周年生态建设者喜迎中秋油中3周年挑战者 lv2

    发表于 2022-9-15 09:56:12 | 显示全部楼层 | 阅读模式

    早上摸鱼时发现的,头次知道AbortController,可以中断Promise,觉得不错,分享一下

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

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

    上不慕古,下不肖俗。为疏为懒,不敢为狂。为拙为愚,不敢为恶。
  • TA的每日心情
    慵懒
    2024-10-28 07:07
  • 签到天数: 193 天

    [LV.7]常住居民III

    712

    主题

    5961

    回帖

    6760

    积分

    管理员

    非物质文化遗产社会摇传承人

    积分
    6760

    荣誉开发者喜迎中秋油中2周年生态建设者

    发表于 2022-9-15 10:03:49 | 显示全部楼层
    这是中断fetch的吧
    混的人。
    ------------------------------------------
    進撃!永遠の帝国の破壊虎---李恒道

    入驻了爱发电https://afdian.net/a/lihengdao666
    个人宣言:この世界で私に胜てる人とコードはまだ生まれていません。死ぬのが怖くなければ来てください。
    回复

    使用道具 举报

  • TA的每日心情
    开心
    昨天 13:37
  • 签到天数: 213 天

    [LV.7]常住居民III

    305

    主题

    4193

    回帖

    4059

    积分

    管理员

    积分
    4059

    管理员荣誉开发者油中2周年生态建设者喜迎中秋油中3周年挑战者 lv2

    发表于 2022-9-15 10:07:50 | 显示全部楼层

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

    这是中断fetch的吧

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

    /**
     * 自定义的可以主动取消的 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); // 可以更改时间看不同的结果
    上不慕古,下不肖俗。为疏为懒,不敢为狂。为拙为愚,不敢为恶。
    回复

    使用道具 举报

  • TA的每日心情
    慵懒
    2022-3-8 11:41
  • 签到天数: 2 天

    [LV.1]初来乍到

    22

    主题

    881

    回帖

    1379

    积分

    荣誉开发者

    积分
    1379

    荣誉开发者卓越贡献油中2周年生态建设者油中3周年挑战者 lv2

    发表于 2022-9-15 12:06:14 | 显示全部楼层
    AbortController中断Promise的效果其实没有那么理想,他只是提前resolve/reject,如果不做任何处理,Promise实际还是在运行的:
    1. function myPromise(signal) { // 吐槽下示例的写法,额外创建了一个对象,还不如把ac传进来算了
    2.     return new Promise(resolve => {
    3.         let i = 0;
    4.         const timer = setInterval(() => {
    5.             console.log(++i);
    6.             if (i === 10) {
    7.                 clearInterval(timer);
    8.                 resolve('done');
    9.             }
    10.         }, 500);
    11.         signal.addEventListener('abort', () => resolve('aborted'));
    12.     });
    13. }
    14. const ac = new AbortController();
    15. const { signal } = ac;
    16. myPromise(signal).then(e => console.log(e));
    17. setTimeout(() => {
    18.     ac.abort();
    19. }, 2000);
    复制代码

    这个例子可以看到,中断后定时器还是在继续执行,这与Promise的特性有关,要正确中断必须自己加clearInterval才行。AbortController只是抛出一个中断信号,具体的中断实现和收尾工作还得自己写,换个角度说,即使不用AbortController,这种“中断”特性也可以轻松实现:
    1. function myPromise(signal) {
    2.     return new Promise(resolve => {
    3.         let i = 0;
    4.         const timer = setInterval(() => {
    5.             console.log(++i);
    6.             if (i === 10) {
    7.                 clearInterval(timer);
    8.                 resolve('done');
    9.             }
    10.         }, 500);
    11.         signal.abort = () => {
    12.             clearInterval(timer);
    13.             resolve('aborted');
    14.         }
    15.     });
    16. }
    17. const signal = {};
    18. myPromise(signal).then(e => console.log(e));
    19. setTimeout(() => {
    20.     signal.abort();
    21. }, 2000);
    复制代码

    至于中断fetch和addEventListener,实际也是因为这些API内部实现了中断逻辑(有一定兼容性问题)。中断Promise听上去很高大上,其实也就那回事,就是同时中断多个比较方便而已。
    回复

    使用道具 举报

  • TA的每日心情
    开心
    昨天 13:37
  • 签到天数: 213 天

    [LV.7]常住居民III

    305

    主题

    4193

    回帖

    4059

    积分

    管理员

    积分
    4059

    管理员荣誉开发者油中2周年生态建设者喜迎中秋油中3周年挑战者 lv2

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

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

    优点就是这是规范,很多地方都有用
    上不慕古,下不肖俗。为疏为懒,不敢为狂。为拙为愚,不敢为恶。
    回复

    使用道具 举报

  • TA的每日心情
    慵懒
    2022-3-8 11:41
  • 签到天数: 2 天

    [LV.1]初来乍到

    22

    主题

    881

    回帖

    1379

    积分

    荣誉开发者

    积分
    1379

    荣誉开发者卓越贡献油中2周年生态建设者油中3周年挑战者 lv2

    发表于 2022-9-15 21:22:25 | 显示全部楼层
    试着手写了一下AbortController的实现,感觉这玩意是真的简单:
    1. class AbortSignal extends EventTarget {
    2.     constructor() {
    3.         super();
    4.         this.aborted = false;
    5.         this.reason = undefined;
    6.         this.onabort = null;
    7.     }
    8.     throwIfAborted() {
    9.         if (this.aborted) throw this.reason;
    10.     }
    11. }
    12. class AbortController {
    13.     constructor() {
    14.         this.signal = new AbortSignal();
    15.     }
    16.     abort(reason = new DOMException('signal is aborted without reason')) {
    17.         this.signal.aborted = true;
    18.         this.signal.reason = reason;
    19.         const evt = new Event('abort');
    20.         this.signal.dispatchEvent(evt);
    21.         if (typeof this.signal.onabort === 'function') this.signal.onabort(evt);
    22.     }
    23. }
    复制代码
    回复

    使用道具 举报

  • TA的每日心情
    开心
    昨天 13:37
  • 签到天数: 213 天

    [LV.7]常住居民III

    305

    主题

    4193

    回帖

    4059

    积分

    管理员

    积分
    4059

    管理员荣誉开发者油中2周年生态建设者喜迎中秋油中3周年挑战者 lv2

    发表于 2022-9-15 21:25:28 | 显示全部楼层
    cxxjackie 发表于 2022-9-15 21:22
    试着手写了一下AbortController的实现,感觉这玩意是真的简单:

    话说这手写的,fetch能用么?
    上不慕古,下不肖俗。为疏为懒,不敢为狂。为拙为愚,不敢为恶。
    回复

    使用道具 举报

  • TA的每日心情
    慵懒
    2022-3-8 11:41
  • 签到天数: 2 天

    [LV.1]初来乍到

    22

    主题

    881

    回帖

    1379

    积分

    荣誉开发者

    积分
    1379

    荣誉开发者卓越贡献油中2周年生态建设者油中3周年挑战者 lv2

    发表于 2022-9-15 22:03:38 | 显示全部楼层
    王一之 发表于 2022-9-15 21:25
    话说这手写的,fetch能用么?

    不行,过不了类型判断,fetch应该有真正的AbortSignal引用。这个AbortSignal目前应该算实验性质的接口,没有提供实例化方法(只能实例化一个已中断的signal),所以我才重新写了一个,以后可能可以直接new吧。
    回复

    使用道具 举报

    发表回复

    本版积分规则

    快速回复 返回顶部 返回列表