实现一个可取消的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

相关推荐
i听风逝夜12 分钟前
Web 3D地球实时统计访问来源
前端·后端
iMonster16 分钟前
React 组件的组合模式之道 (Composition Pattern)
前端
呐呐呐呐呢24 分钟前
antd渐变色边框按钮
前端
元直数字电路验证43 分钟前
Jakarta EE Web 聊天室技术梳理
前端
wadesir1 小时前
Nginx配置文件CPU优化(从零开始提升Web服务器性能)
服务器·前端·nginx
牧码岛1 小时前
Web前端之canvas实现图片融合与清晰度介绍、合并
前端·javascript·css·html·web·canvas·web前端
灵犀坠1 小时前
前端面试八股复习心得
开发语言·前端·javascript
9***Y481 小时前
前端动画性能优化
前端
网络点点滴1 小时前
Vue3嵌套路由
前端·javascript·vue.js
牧码岛1 小时前
Web前端之Vue+Element打印时输入值没有及时更新dom的问题
前端·javascript·html·web·web前端