背景
实现一个函数 接受一个promise 将一个promise转换为一个可取消的promise。
分析
首先我们返回的必然是一个符合 promise A+ 规范的promise对象,需要支持所有的promise api和调用方式。
cancel
这个promise需要可以取消 我们可以在这个返回的实例上添加一个cancel
api, 当用户调用这个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
cancel
用于实现取消的逻辑isCancel
用于判断是否真的取消了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 }
]