实现一个可取消的promise

背景

实现一个函数 接受一个promise 将一个promise转换为一个可取消的promise。

分析

首先我们返回的必然是一个符合 promise A+ 规范的promise对象,需要支持所有的promise api和调用方式。

cancel

这个promise需要可以取消 我们可以在这个返回的实例上添加一个cancelapi, 当用户调用这个api表明要取消当前的promise.

因为promise的执行特性 本质上一个存储状态和结果以及回调函数的容器

具体参考这里js三座大山之异步三promise本质

而状态是可以同步改变的也可以异步改变 但是读取结果一定是异步的。

例如

javascript 复制代码
const p3 = () =>
  new Promise((resolve) => {
    console.log("Promise内部(同步)");
    resolve(33333);
  });

console.log("开始");
p3().then((res) => {
  console.log("test res", res);  // 异步输出
});
console.log("结束");

输出顺序会是这样的:

bash 复制代码
开始
Promise内部(同步)
结束
test res 33333

这显示了即使Promise的状态改变是同步执行的(即,resolve(33333)时),此时Promise内部容器的状态就已经发生了改变 但是通过.then()注册的回调函数是在当前JavaScript执行上下文之外,也就是异步执行的。这是Promise规范设计的一个重要部分,旨在确保Promise处理总是以预测的方式异步进行,即使Promise立即兑现。

isCancel

所以当调用promise获取结果的时候 可能在没有来得及执行cancel时 promise的状态就已经被改变了。

因此 我们需要一个isCancel的api来判断 当调用了cancel后当前的promise是否真的被取消了。

Promise.race + Symbol

当调用cancel时 promise被取消 实际上我们会通过reject去更改状态 这会导致promise报错抛出异常 为了不影响业务代码 所以我们需要通过catch来捕获 并验证这个异常是用户主动取消引起的还是程序执行引起的。如若果是用户主动取消的 说明是符合我们预期的.

所以我们需要在调用cancel时 通过Symbol作为唯一标记,然后在catch里面比较 判断这个异常是否是我们主动抛出的。

总结下:

返回的promise实例 应该挂载两个api

  1. cancel 用于实现取消的逻辑
  2. isCancel 用于判断是否真的取消了
  3. cancel实现通过Promise.race + Symbol

代码

ini 复制代码
/**
 * 将一个promise转换为一个可取消的promise
 * @param {Promise} task 希望被转换的promise实例
 * @returns {Promise} 返回具有cancel()&isCancel()的promise对象
 */
export const TaskCancelable = (task: Promise<any>) => {
    let _reject: Resolve;
    let isCancel = false;
    const _status = Symbol("cancel");
    const cancelP = new Promise((resolve, reject) => {
      _reject = reject;
    });
    const p = Promise.race([task, cancelP]) as PromiseCancel;
    /***
     * 调用cancel时可能promise状态已经变为成功,
     * 所以不能在cancel里面改变isCancel
     * 只有catch的原因是cancel才代表被取消成功了
     */
    p.catch((reason) => {
     // 注意这里的比较 只有报错的原因是我们的symbol 才是用户手动取消的
      if (reason === _status) { 
        isCancel = true;
      }
    });
  
    p.cancel = () => {
      _reject(_status);
      return p;
    };
    p.isCancel = () => {
      return isCancel;
    };
    return p;
  };

使用

主动取消的情况

javascript 复制代码
const p3 = () =>
  new Promise((resolve) => {
    setTimeout(() => {
      resolve(33333);
    }, 2000);
  });
const cancelP = TaskCancelable(p3());

cancelP
  .cancel()
  .then((res) => {
    console.log("获取结果 res", res);
  })
  .catch((reason) => {
    if (cancelP.isCancel()) {
      // true
      console.log("promise 被用户主动cancel了: ", cancelP.isCancel());
    } else {
      console.log("这里捕获业务异常 reason", reason);
    }
  })
  .finally(() => {
    console.log("finally done"); // true
  });
  
打印结果:
promise 被用户主动cancel了:  true
finally done

不取消的情况

javascript 复制代码
cancelP
  .then((res) => {
    console.log("获取结果 res", res);
  })
  .catch((reason) => {
    if (cancelP.isCancel()) {
      // true
      console.log("promise 被用户主动cancel了: ", cancelP.isCancel());
    } else {
      console.log("这里捕获业务异常 reason", reason);
    }
  })
  .finally(() => {
    console.log("finally done"); // true
  });

打印结果:
获取结果 res 33333
finally done

reject的情况

javascript 复制代码
const p3 = () =>
  new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(33333);
    }, 2000);
  });
const cancelP = TaskCancelable(p3());

cancelP
  .then((res) => {
    console.log("获取结果 res", res);
  })
  .catch((reason) => {
    if (cancelP.isCancel()) {
      // true
      console.log("promise 被用户主动cancel了: ", cancelP.isCancel());
    } else {
      console.log("这里捕获业务异常 reason", reason);
    }
  })
  .finally(() => {
    console.log("finally done"); // true
  });

// 打印结果 
这里捕获业务异常 reason 33333
finally done

接口其他Promise api的情况

javascript 复制代码
const p1 = new Promise((resolve) => {
  setTimeout(() => {
    resolve(1111);
  }, 1000);
});

const p2 = new Promise((resolve) => {
  setTimeout(() => {
    resolve(2222);
  }, 1500);
});

const p3 = () =>
  new Promise((resolve) => {
    setTimeout(() => {
      resolve(33333);
    }, 2000);
  });
const cancelP = TaskCancelable(p3());

Promise.allSettled([cancelP, p1, p2]).then(res => {
  console.log('test res: ', res);
})

打印结果:
test res:  [
  { status: 'fulfilled', value: 33333 },
  { status: 'fulfilled', value: 1111 },
  { status: 'fulfilled', value: 2222 }
]

参考

task-cancelable

相关推荐
牧羊狼的狼30 分钟前
React 中的 HOC 和 Hooks
前端·javascript·react.js·hooks·高阶组件·hoc
知识分享小能手2 小时前
React学习教程,从入门到精通, React 属性(Props)语法知识点与案例详解(14)
前端·javascript·vue.js·学习·react.js·vue·react
luckys.one2 小时前
第9篇:Freqtrade量化交易之config.json 基础入门与初始化
javascript·数据库·python·mysql·算法·json·区块链
魔云连洲2 小时前
深入解析:Vue与React的异步批处理更新机制
前端·vue.js·react.js
mCell2 小时前
JavaScript 的多线程能力:Worker
前端·javascript·浏览器
weixin_437830944 小时前
使用冰狐智能辅助实现图形列表自动点击:OCR与HID技术详解
开发语言·javascript·ocr
超级无敌攻城狮4 小时前
3 分钟学会!波浪文字动画超详细教程,从 0 到 1 实现「思考中 / 加载中」高级效果
前端
excel5 小时前
用 TensorFlow.js Node 实现猫图像识别(教学版逐步分解)
前端
gnip5 小时前
JavaScript事件流
前端·javascript