本文围绕 ECMAScript 2024 的一个新特性 Promise.withResolvers,从基础介绍、使用场景、兼容处理方案三个方面展开探讨。
Promise.withResolvers是什么?
MDN 文档对于 Promise.withResolvers 的介绍是这样的,
Promise.withResolvers()静态方法返回一个对象,其包含一个新的Promise对象和两个函数,用于解决或拒绝它,对应于传入给Promise()构造函数执行器的两个参数。
也就是说,它创造了一个新的 Promise 实例以及与之关联的 resolve 与 reject 方法。
它的出现,引入了创建 Promise 实例的新方式,在此之前我们创建 Promise 实例是这样的:
const promiseInstance = new Promise<void>((resolve,reject)=>{
// ...
})
现在,我们也能够通过 Promise.withResolvers 来创建实例:
const { promise, resolve, reject } = Promise.withResolvers<void>()
但它并不是为了替代原有创建 Promise 实例的方式而出现,引入它实际上是为了 打破 构造器模式创建 Promise 实例时,resolve 与 reject 方法只能在构造器回调函数中使用的 限制。
在它出现之前,我们也可以通过如下代码实现相同的效果:
let resolve, reject
const promise = new Promise((res, rej) => {
resolve = res
reject = rej
})
只是 Promise.withResolvers 更简洁,同时标准化了该实现。
适合运用的场景?
Promise.withResolvers 使得 Promise 实例与其 resolve、reject 方法处于同一作用域内,也就解放了只能在构造器回调中调用的限制,执行器中的逻辑代码可以被进一步拆分简化,同时减少嵌套层级。
适合运用的场景:在重复的事件中重复使用,特别是在处理流和队列时。
比如如下事件聚合管理器的例子:
// 代码场景来源:https://pawelgrzybek.com/deferred-javascript-promises-using-promise-withresolvers
/**
* 创建事件聚合管理器
* @param eventMaxCount 最大事件数,当事件队列数达到该值时,标记为完成聚合并返回事件队列
*/
function createEventsAggregator(eventMaxCount: number) {
const events: string[] = []
const { promise, resolve, reject } = Promise.withResolvers()
return {
/** 添加推送事件的方法 */
add: (event: string) => {
// 如果当前事件队列数小于最大事件数,则添加事件
if (events.length < eventMaxCount) events.push(event)
// 如果当前事件队列数达到最大事件数,则标记为完成聚合并返回事件队列
if (events.length === eventMaxCount) resolve(events)
},
/** 取消聚合事件的方法 */
abort: () => reject("Events aggregation aborted."),
/** 事件聚合的promise实例 */
events: promise,
}
}
const eventsAggregator = createEventsAggregator(3)
eventsAggregator.events
.then(events => console.log("Resolved:", events))
.catch(reason => console.error("Rejected:", reason))
eventsAggregator.add("event-one")
eventsAggregator.add("event-two")
eventsAggregator.add("event-three")
// Resolved: [ "event-one", "event-two", "event-three" ]
如何在vite项目中实现兼容?
从 MDN 上对于该 API 的浏览器兼容性介绍上可以看到,Chrome 浏览器最低支持的版本为 119,对于不支持该特性的浏览器环境,我们需要使用 polyfill 来保证代码的兼容性。
core-js 已经为我们提供了polyfill,我们需要做的是在项目中进行兼容配置,这里以 vite 项目为例。
在
vite项目中,常用@vitejs/plugin-legacy插件实现项目打包的代码兼容功能。 如果考虑打包性能问题,可以使用vite-plugin-legacy-swc,其打包耗时相比于@vitejs/plugin-legacy有很大的提升,打包后的资源体积也略微少一点。
首先,我们新建一个 vite 项目。
pnpm create vite
删除冗余模板代码,只在项目入口文件处保留如下关于 Promise.withResolvers 的测试代码,用于验证兼容性。
// 项目入口文件 main.ts
const { promise, resolve } = Promise.withResolvers<string>()
setTimeout(() => {
resolve("done")
}, 500)
promise.then(res => {
console.log("Promise.withResolvers-resolved:", res)
})
执行 pnpm run build 打包,打包结果如下:
运行预览打包资源 pnpm run preview,打开控制台可以看到:
而运行在 chrome119版本以下的浏览器环境会存在报错,报错原因是 Promise 对象上没有 withResolvers 方法。
下面我们引入 vite-plugin-legacy-swc 插件并在 vite.config.ts 配置文件中使用,
pnpm add vite-plugin-legacy-swc -D
在 vite.config.ts 文件中配置使用:
import { defineConfig } from "vite"
import legacy from "vite-plugin-legacy-swc"
export default defineConfig({
base: "./",
plugins: [
legacy({
modernPolyfills: ["esnext.promise.with-resolvers"],
// 禁用为兼容传统浏览器生成的legacy chunks,仅将polyfill注入modern构建
renderLegacyChunks: false
})
]
})
这里有必要说明一下,因为
Promise.withResolversAPI 在chrome119 版本才支持,所以这里需要使用modernPolyfills配置项为现代版本浏览器生成polyfills块。至于
polyfill的兼容模块写法规则,比如这里Promise.withResolvers对应模块规则为什么是esnext.promise.with-resolvers,可到 UNPKG - core-js 上对应查找,只有符合规则在项目打包时才能定位到core-js中相应的兼容模块。
执行 pnpm run build 打包,打包结果如下,可以看到相比于兼容前多了一个 polyfills 文件:
这样便可成功兼容支持 ES modules 的现代浏览器了😋。
加料 - 兼容插件打包对比
上面既然提到了 @vitejs/plugin-legacy 与 vite-plugin-legacy-swc 两个 vite 关于兼容的插件,那这里不妨再花一点时间进行总结对比。
关于兼容配置,以上兼容配置写法同样适用于
@vitejs/plugin-legacy插件,这里仅需再安装@vitejs/plugin-legacy依赖替换掉vite-plugin-legacy-swc即可。
| 未兼容 | @vitejs/plugin-legacy兼容方案 | vite-plugin-legacy-swc兼容方案 | |
|---|---|---|---|
| 打包耗时 | 88ms | 504ms | 288ms |
| 打包资源大小 | index.html:0.39kB index.js:0.86kB | index.html:0.48kB index.js:0.86kB polyfills.js:9.97kB | index.html:0.48kB index.js:0.86kB polyfills.js:9.62kB |
可以看到,vite-plugin-legacy-swc插件的打包性能相对更高。
结语
首次发文,感谢你的阅读支持❤️,希望本文能帮助到各位。如有不足或错误之处,欢迎批评指正~