core-js 中的 Await dictionary 提案:Promise.allKeyed 与 Promise.allSettledKeyed 完全指南
2026/9/11 15:58:45 网站建设 项目流程

core-js 中的 Await dictionary 提案:Promise.allKeyed 与 Promise.allSettledKeyed 完全指南

【免费下载链接】core-jsStandard Library项目地址: https://gitcode.com/GitHub_Trending/co/core-js

导读

Await dictionary 是 TC39 正在推进的 JavaScript 提案(proposal-await-dictionary),其核心目标是为开发者提供一种“按对象键名组织 Promise、统一等待结果”的并发原语:Promise.allKeyedPromise.allSettledKeyed。本文以 docs/web/docs/features/proposals/await-dictionary.md 为骨架,结合 core-js 仓库中的模块源码与 QUnit 测试用例,系统讲解这两个方法的类型签名、语义细节、安装入口、使用示例与底层实现原理,帮助你直接在当前项目中使用这一提案能力。

提案背景:为什么需要 "Keyed" 并发方法

在现有标准 API 中,Promise.all接受一个可迭代对象(通常是数组),结果也以数组形式返回。当并发任务的来源是一组带名字的配置项、命名资源或字典结构时,开发者不得不在“数组 + 手动映射回键名”之间来回转换,既啰嗦又容易在索引上出错。

Await dictionary 提案的出发点是:允许你直接传入一个普通对象(字典),键名即结果键名Promise.allKeyed在语义上相当于Object.fromEntriesPromise.all的组合,而Promise.allSettledKeyed则是“keyed 版”的Promise.allSettled,即使个别 Promise 失败,也会收集所有键的最终状态而不会整体拒绝。

从源码注释可以看到,core-js 正是依据该提案实现这两个方法的,见 esnext.promise.all-keyed.js 与 esnext.promise.all-settled-keyed.js 中的注释:// Promise.allKeyed method/// Promise.allSettledKeyed method,均标注提案地址https://github.com/tc39/proposal-await-dictionary

模块与内置方法签名

原文档明确了该提案在 core-js 中由两个模块承载:

模块文件(相对仓库根目录)提供的方法
esnext.promise.all-keyed.jsPromise.allKeyed
esnext.promise.all-settled-keyed.jsPromise.allSettledKeyed

对应到原生内置对象的 TypeScript 签名如下(继承自原文档):

class Promise { allKeyed<T extends Record<string, unknown>>( obj: T ): Promise<{ [K in keyof T]: Awaited<T[K]> }>; allSettledKeyed<T extends Record<string, unknown>>( obj: T ): Promise<{ [K in keyof T]: PromiseSettledResult<Awaited<T[K]>> }>; }

需要说明的语义要点:

  • allKeyed返回的 Promise 在所有键对应的值全部 resolve 后兑现;只要有一个值 reject,整个结果就会以该错误 reject(与Promise.all的“快速失败”行为一致)。
  • allSettledKeyed永不因单个值 reject 而整体拒绝,而是为每个键生成{ status: 'fulfilled', value }{ status: 'rejected', reason }形式的PromiseSettledResult(与Promise.allSettled行为一致)。
  • 两者都接受普通值(非 Promise 值会被直接当作已兑现处理),这一点在测试 esnext.promise.all-keyed.js 中有明确验证。

使用示例

以下是原文档给出的标准示例,直接展示了“字典输入、字典输出”的用法:

await Promise.allKeyed({ a: Promise.resolve(1), b: Promise.resolve(2), c: 3, }); // => { a: 1, b: 2, c: 3 } await Promise.allSettledKeyed({ a: Promise.resolve(1), b: Promise.reject(2), c: 3, }); // => { a: { status: "fulfilled", value: 1 }, b: { status: "rejected", reason: 2 }, c: { status: "fulfilled", value: 3 } }

典型场景:带名字的并发请求

假设需要同时请求三个具名资源并保持结构对应:

const result = await Promise.allKeyed({ user: fetch('/api/user').then(r => r.json()), posts: fetch('/api/posts').then(r => r.json()), settings: fetch('/api/settings').then(r => r.json()), }); // result.user / result.posts / result.settings 一一对应,无需再手动 zip

相比Promise.all返回数组后再按索引取值,allKeyed让每个异步结果天然拥有语义化键名,代码可读性与可维护性都更好。

安装与使用入口

原文档给出了三种入口方式(Entry points):

core-js/proposals/await-dictionary core-js(-pure)/actual|full/promise/all-keyed core-js(-pure)/actual|full/promise/all-settled-keyed

它们的实际对应关系与使用姿势如下。

1. 提案聚合入口:一次引入两个方法

仓库中的 packages/core-js/proposals/await-dictionary.js 是一个聚合模块,内部仅做两件事:

require('../modules/esnext.promise.all-keyed'); require('../modules/esnext.promise.all-settled-keyed');

因此你只需一次性引入即可同时获得两个方法:

import 'core-js/proposals/await-dictionary'; // 或 CommonJS require('core-js/proposals/await-dictionary'); await Promise.allKeyed({ a: Promise.resolve(1) }); await Promise.allSettledKeyed({ a: Promise.resolve(1) });

2. 单一方法入口:按需加载

若只想引入其中一个方法,可以使用精确到方法的入口(以 actual/full 版本为例,文件位于 packages/core-js/actual/promise/all-keyed.js 与 packages/core-js/actual/promise/all-settled-keyed.js):

import 'core-js/actual/promise/all-keyed'; import 'core-js/actual/promise/all-settled-keyed'; // 或 require('core-js/actual/promise/all-keyed'); require('core-js/actual/promise/all-settled-keyed');

full/actual/的区别在于:actual入口只包含按提案最新状态可用的实现,full则包含 core-js 的全部特性集。core-js-pure版本(不污染全局原型)同样支持这两个入口,只是引入前缀为core-js-pure/。关于入口点的完整说明,可参考 docs/web/docs/usage.md 中 “Entry points” 一节的约定。

// core-js-pure 版本示例 const { allKeyed } = require('core-js-pure/actual/promise/all-keyed');

源码级实现原理

allKeyed 的核心流程

esnext.promise.all-keyed.js 的实现要点如下:

  1. 通过newPromiseCapabilityModule.f(C)创建 Promise 能力(capability),this指向调用时的构造函数,保证子类化语义。
  2. 通过Reflect.ownKeys拿到输入对象的全部自有键(含 Symbol 键),再用getOwnPropertyDescriptor过滤出可枚举(enumerable)自有属性——这一点与Object.keys的语义保持一致。
  3. 对每个可枚举键调用C.resolve(promises[key])统一包装为 Promise(普通值也会被 resolve),随后.then注册兑现回调。
  4. 使用remaining计数器跟踪未完成数量,全部完成后用create(null)构造一个null 原型的结果对象,并通过createProperty按原键名回填结果,最终resolve(res)
  5. 任意一个值 reject 时直接调用reject,整体失败。

allSettledKeyed 的差异

esnext.promise.all-settled-keyed.js 与allKeyed结构几乎一致,唯一区别在于第 33-45 行的createElementResolver辅助函数:它对每个值同时注册fulfilledrejected两条回调路径,无论结果如何都会把{ status: 'fulfilled', value }{ status: 'rejected', reason }写入对应位置,因此整体永远不会因单个值的失败而拒绝。

关键行为总结(均有测试佐证)

仓库中的单元测试(tests/unit-global/esnext.promise.all-keyed.js 与 tests/unit-global/esnext.promise.all-settled-keyed.js)对以下行为做了完整覆盖:

行为说明测试位置
支持原始值非 Promise 值直接按已兑现处理两个测试文件的 "resolved with primitives" 用例
空对象传入{}立即兑现为空对象"resolved with empty object" 用例
忽略不可枚举与原型属性只处理输入对象自身的可枚举键,原型链上的键被忽略"resolved with hidden attributes" 用例
Symbol 键支持结果对象保留 Symbol 键"symbol keys" 用例
结果顺序稳定即使 Promise 以不同顺序完成,结果键顺序仍与输入一致(依赖索引槽位机制)"keys order" 用例
结果对象 null 原型Object.getPrototypeOf(result) === null"result object has null prototype" 用例
子类化支持allKeyed.call(SubPromise, ...)返回SubPromise实例"subclassing" 用例
非法输入拒绝传入字符串等非对象会以TypeError拒绝"rejected on incorrect input" 用例

以“隐藏属性”用例为例,输入对象Object.create({ proto: Promise.resolve('hidden') })且含一个enumerable: falseinvisible键时,结果只包含{ visible: 42 },证明实现严格遵循“仅可枚举自有键”的语义。

延迟兑现下的键序保持

由于allKeyed在遍历时用keys[index] = keyvalues[index] = undefined预先占位,再在回调中按index回填,因此即便较慢的 Promise 最后完成,结果对象的键顺序依然等于输入对象的键顺序。测试 "keys order" 专门构造了b延迟 10ms 兑现的场景来验证这一点。

与 Promise.all / allSettled 的对比

特性Promise.allPromise.allKeyedPromise.allSettledPromise.allSettledKeyed
输入形态可迭代对象(数组等)对象(字典)可迭代对象对象(字典)
输出形态数组按原键名组织的对象PromiseSettledResult[]数组按原键名组织的PromiseSettledResult对象
快速失败否(汇总所有状态)否(汇总所有状态)
键名语义无(索引定位)有(语义化定位)

如果你的并发任务天然以“命名资源”形式存在(配置加载、具名请求、分组校验等),keyed 系列方法能显著减少“数组 ↔ 字典”之间的人工映射代码。

注意事项与版本现状

  • 该能力目前属于esnext(提案)阶段特性,对应模块文件均以esnext.前缀命名,尚未进入标准库;依赖原生Promise.allKeyed的代码在绝大多数运行时中不可用,必须通过 core-js 或 core-js-pure 引入。
  • 引入后是向Promise构造函数添加静态方法,位于 packages/core-js/modules/esnext.promise.all-keyed.js 的$({ target: 'Promise', stat: true }, ...)中,stat: true表示挂载为静态方法而非原型方法。
  • core-js-pure(不污染全局)场景下,方法通过 packages/core-js/actual/promise/all-keyed.js 中的包装函数导出:调用时若this可调用则作为构造上下文传入,否则回退到原生Promise
  • 由于提案仍在演进,具体语义(如是否处理 Symbol 键、结果对象原型等)以当前 core-js 版本实现与 TC39 提案最新文本为准。

小结

Await dictionary 提案为 JavaScript 并发编程补上了一块实用拼图:Promise.allKeyed让你用字典组织并发任务并保持键名语义,Promise.allSettledKeyed则提供“全量汇总、永不整体失败”的 keyed 版 allSettled。通过 packages/core-js/proposals/await-dictionary.js 或两个独立入口即可在当前项目中启用,其内部实现与测试用例(esnext.promise.all-keyed.js、esnext.promise.all-settled-keyed.js)为理解语义边界(可枚举自有键、Symbol 键、null 原型、键序稳定、子类化)提供了完整的可验证依据,值得在涉及命名并发任务的场景中优先采用。

【免费下载链接】core-jsStandard Library项目地址: https://gitcode.com/GitHub_Trending/co/core-js

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询