For AI agents: the complete documentation index is available at /tc39-atlas/llms.txt, the full documentation bundle is available at /tc39-atlas/llms-full.txt, and this page is available as Markdown at /tc39-atlas/proposals/proposal-await-dictionary.md.
  • 简体中文
  • Await Dictionary S3

    中文标题:Await 字典

    提案概览
    提案速览

    该提案解决的是等待多个具名异步值时,每个 await 串行执行或依赖 Promise.all 按位置返回结果的问题。它引入 Promise.allKeyed,接收一个 Promise 对象并返回一个 Promise,其结果对象的键对应已等待的值。它还新增 Promise.allSettledKeyed,用于按键返回 settled 结果,并遵循 joint iteration 提案的结构。

    Note

    以下 README 来自上游仓库,其中的阶段或状态标注可能滞后;当前信息以提案概览为准。

    Promise 的 Await 字典

    状态

    阶段:3

    提案推进人:

    作者:

    动机

    对各个属性分别使用 await 会形成瀑布式流程,而不是并行运行请求:

    const obj = {
      shape: await getShape(),
      color: await getColor(),
      mass: await getMass(),
    };

    Promise.all 有所帮助,但它基于顺序而非名称,这可能导致混淆:

    const [
      color,
      shape,
      mass,
    ] = await Promise.all([
      getShape(),
      getColor(),
      getMass(),
    ]);

    使用现有语法的解决方案可能很冗长,并且会 污染 作用域中的变量数量:

    const shapeRequest = getShape();
    const colorRequest = getColor();
    const massRequest = getMass();
    
    const shape = await shapeRequest;
    const color = await colorRequest;
    const mass = await massRequest;

    此外,上述模式有产生未处理 Promise 拒绝的风险。如果 await shapeRequest 抛出异常,那么 colorRequest 或 massRequest 这两个 Promise 没有附加任何处理器——如果其中任一 Promise 拒绝,就会导致未处理的 Promise 拒绝错误。在某些系统上,这会导致进程退出。

    提议的解决方案

    const {
      shape,
      color,
      mass,
    } = await Promise.allKeyed({
      shape: getShape(),
      color: getColor(),
      mass: getMass(),
    });

    这有意地遵循 https://github.com/tc39/proposal-joint-iteration 的结构。

    正如 Promise.all 之于 Iterator.zip

    Iterator.zip = (Array<Iterator<T>>) => Iterator<Array<T>>
    Promise.all  = (Array<Promise<T>>)  => Promise<Array<T>>

    Promise.allKeyed 之于 Iterator.zipKeyed

    type Dict<V> = { [k: string | symbol]: V };
    
    Iterator.zipKeyed = <D extends Dict<Iterator<any>>>(iterables: D)
      => Iterator<{ [k in keyof D]: Nexted<D[k]> }>
    
    Promise.allKeyed  = <D extends Dict<Promise<any>>>(promises: D)
      => Promise <{ [k in keyof D]: Awaited<D[k]> }>

    附加 API

    该提案还将此功能扩展到 Promise.allSettled 的使用者。

    const results = await Promise.allSettledKeyed({
        shape: getShape(),
        color: getColor(),
        mass: getMass(),
    });
    if (results.shape.status === "fulfilled") {
      console.log(results.shape.value);
    } else {
      console.error(results.shape.reason)
    }

    现有解决方案

    库自有符号
    Bluebird.props✅❌
    combine-promises✅❌
    p-props✅❌

    实现

    Polyfill/转译器实现

    原生实现

    无。

    问答

    为什么不提供深拷贝选项?

    抛开 JSON.stringify 不谈,JavaScript 内置 API 深度遍历任意对象并不常见。

    Array.prototype.flat 是 深层 的,但仅限数组定义明确的边界。

    为什么只处理自有键?

    这遵循了 Object.keys 等其他内置方法,也与 https://github.com/tc39/proposal-joint-iteration 一致。

    那符号键呢?

    所有可枚举属性都会被使用,包括可枚举符号。

    这与 https://github.com/tc39/proposal-joint-iteration 一致。

    这确实不同于现有解决方案,后者遵循 Object.keys 语义(忽略符号)。由于自有可枚举符号的使用率很低,这一差异不被认为是个问题。

    考虑过的替代方案

    Promise.ownProperties

    const {
      shape,
      color,
      mass,
    } = await Promise.ownProperties({
      shape: getShape(),
      color: getColor(),
      mass: getMass(),
    });

    Promise.fromEntries

    const {
      shape,
      color,
      mass,
    } = await Promise.fromEntries(Object.entries({
      shape: getShape(),
      color: getColor(),
      mass: getMass(),
    }));

    Promise.all 重载

    根据参数是否可迭代进行分派。

    const {
      shape,
      color,
      mass,
    } = await Promise.all({
      shape: getShape(),
      color: getColor(),
      mass: getMass(),
    });

    "这 会 避免向 API 表面引入新名称。然而,输出形态取决于输入形态这一点令人不适,而且存在意外传入多个参数而不是数组的风险。例如:

    Promise.all(p1, p2, p3); // ❌ should have been `Promise.all([p1, p2, p3])`

    虽然目前由于 p1 不可迭代,这会抛出异常,但该重载会开始允许这种调用,却不会执行调用者意图的操作。

    专用语法

    受 Swift 等其他语言启发——参见:https://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency#Calling-Asynchronous-Functions-in-Parallel

    async const shape = getShape();
    async const color = getColor();
    async const mass = getMass();
    
    const obj = await {
      shape,
      color,
      mass: Math.max(0, mass),
    };

    在 await <exp> 中对 async const 标识符的所有引用都会被隐式等待。

    上面的代码(大致)等价于:

    const $0 = getShape();
    const $1 = getColor();
    const $2 = getMass();
    
    const obj = await ((shape, color, mass) => ({
      shape,
      color,
      mass: Math.max(0, mass),
    }))(await $0, await $1, await $2);