Promise 加入 JavaScript 时日已久,它为 JavaScript 提供了全新的异步编程方式。自从 async 与 await 关键字的加入,更是让我们彻底摆脱了链式调用。
但 Promise 仍然存在问题,由于它最初的设计使得我们只能在一个新的上下文中开始异步逻辑,这对一些特殊的流程控制模式与逻辑复用造成了麻烦。特别是 async 与 await 出现后,这种模式无法充分利用 async 与 await 提供的可以在一个上下文执行全部异步逻辑的优势。因此我们不得不编写代码将 Promise 的 resolve 与 reject 回调暴露到我们希望的上下文中。
例如笔者就会使用这样的一个模块来将 resolve 与 reject 暴露出来:
var resolve = null,
reject = null,
promise = null;
function expose(function1, function2) {
resolve = function1;
reject = function2;
}
export function promiseWithResolvers() {
promise = new Promise(expose);
const object = { promise, resolve, reject };
promise = resolve = reject = null;
return object;
};
export default promiseWithResolvers;
而现在,ES2023 规范推出了一个新的内置方法来完成这一操作:
/**
* @returns {
* {
* promise: PromiseLike,
* resolve: (result: any) => void,
* reject: (error: any) => void
* }
* }
*/
Promise.withResolvers()
这个方法在被调用后会返回一个对象,这个对象包含一个 Promise 对象,以及与此 Promise 对象关联的 resolve 和 reject 回调,这样,便可以在调用此方法的上下文使用这两个回调。
async function example() {
const { promise, resolve, reject } = Promise.withResolvers();
eventTarget.addEventListener("click" resolve);
await promise;
// ...
}
自此,我们不再需要手动编写暴露 resolve 与 reject 回调的代码。不过,由于这个规范在 2023 年 12 月才发布,现在(2024 年 3 月)各浏览器才刚实现不久,考虑到浏览器新版本普及需要时间,仍需要等待一段时间我们才能无顾虑地将其运用在项目中。
应用
有时我们需要将事件回调封装成 Promise:
new Promise(function(resolve, reject) {
eventTarget.addEventListener("success", resolve);
eventTarget.addEventListener("failed", reject);
});
Promise 能够很轻松的应付这个任务。但是,如果需要接收外部参数呢?很遗憾,我们写给 Promise 的函数的参数已经被用于接收 resolve 和 reject 回调了,Promise 的构造函数也没有被设计成能够接收额外参数并传给我们的函数的模式。这也意味着,我们写给 Promise 的函数是没有办法复用的。
有了Promise.withResolvers()
后上面的逻辑就可以改写成下面的形式,并且能兼顾接收参数和复用的需求:
function wrap(arg1) {
const {resolve, reject, promise} = Promise.withResolvers();
eventTarget.addEventListener(arg1 ? "success" : "default", resolve);
eventTarget.addEventListener("failed", reject);
return promise;
}
wrap(true);
async function example() {
await wrap(false);
}