Array.fromAsync S4
- 阶段: Stage 4
- 状态: 已完成
- ECMAScript 版本: ES2026
- 同步时间: 2026年8月26日
- English original · 官方仓库
该提案添加了一个 Array.fromAsync 静态方法,用于将异步可迭代对象、同步可迭代对象和类数组对象异步转换为数组,类似于 Array.from 但面向异步迭代。它支持可选的映射函数和 thisArg,匹配 for await 的语义,包括惰性迭代和错误处理,并且是一个泛型工厂方法。
以下 README 来自上游仓库,其中的阶段或状态标注可能滞后;当前信息以提案概览为准。
用于 JavaScript 的 Array.fromAsync
ECMAScript 第 4 阶段(待编辑审查)提案。J. S. Choi,2021–2025。
- 规范 可用
- 实验性 polyfill(请勿在生产代码中使用):
为什么需要 Array.fromAsync 方法
自从在 JavaScript 中标准化以来,Array.from 已成为 Array 最常用的内置方法之一。然而,对于异步迭代器,尚不存在类似的功能。
这样的功能对于将异步迭代器的全部内容转储到单个数据结构中也很有用,尤其是在单元测试或命令行界面中。(后面章节包含几个真实世界的示例。)
有一个 it-all NPM 库仅执行此任务,每周下载量约 50,000 次。这当然不包括任何使用带有空数组的临时 for await–of 循环的代码。进一步证明了对此类功能的需求,多个 Stack Overflow 问题 已经由不同的开发者提出,询问如何将异步迭代器转换为数组。
本解释器的后面列出了几个 真实世界的示例。
描述
(可获取正式草案规范。)
Array.fromAsync 之于 for await
正如 Array.from 之于 for。
与 Array.from 类似,Array.fromAsync 将是 Array 内置类的静态方法,带有一个必需参数和两个可选参数:(items, mapfn, thisArg)。
异步可迭代输入
但是,Array.fromAsync 不是将同步可迭代对象转换为数组,而是可以将异步可迭代对象转换为promise,该 promise(如果一切顺利)将解析为一个新数组。在 promise 解析之前,它将从输入创建一个异步迭代器,惰性迭代它,并将每个产生的值添加到新数组中。(无论如何,promise 在 Array.fromAsync 函数调用后立即返回。)
同步可迭代输入
如果参数是同步可迭代对象(而不是异步可迭代对象),则返回值仍然是一个将解析为数组的 promise。如果同步迭代器产生 promises,则每个产生的 promise 在将其值添加到新数组之前都会被等待。(非 promise 的值也会被等待,以防止 Zalgo。)所有这些都符合 for await 的行为。
与 for await 一样,Array.fromAsync 对同步但非异步的输入进行惰性迭代。每当开发者需要将产生 promises 的同步输入转储到数组中时,开发者需要在 Array.fromAsync 和 Promise.all 之间谨慎选择,它们具有互补的控制流:
同样与 for await 一样,当给定同步但非异步的可迭代输入时,Array.fromAsync 将只捕获其迭代到达的第一个拒绝,并且仅当该拒绝不发生在迭代到达并等待它之前的微任务中。更多信息请参见 § 错误。
不可迭代的类数组输入
Array.fromAsync 的有效输入是 Array.from 有效输入的超集。这包括不可迭代的类数组:具有 length 属性以及索引元素的对象(类似于 Array.prototype.values)。返回值仍然是一个将解析为数组的 promise。如果类数组对象的元素是 promises,则每个访问到的 promise 在将其值添加到新数组之前都会被等待。
一位 TC39 代表的意见:“[类数组]不会过时,而且非常不错的是,事物不必强制实现迭代器协议就能转换为数组。”
与其对同步但非异步的可迭代输入所做的一样,Array.fromAsync 惰性迭代类数组输入的值,并等待每个值。开发者必须在 Array.fromAsync 和 Promise.all 之间进行选择(参见 § 同步可迭代输入 和 § 错误)。
泛型工厂方法
Array.fromAsync 是一个泛型工厂方法。它不要求其 this 接收者是 Array 构造函数。fromAsync 可以被任何其他构造函数转移或继承。在这种情况下,最终结果将是由该构造函数(不传参数)创建的数据结构,并将输入产生的每个值赋给该数据结构的数字属性。(完全不涉及 Symbol.species。)如果 this 接收者不是构造函数,则 fromAsync 照常创建数组。这符合 Array.from 的行为。
可选参数
Array.fromAsync 有两个可选参数:mapfn 和 thisArg。
映射函数
mapfn 是一个可选的映射回调,它对输入产生的每个值调用,并附带其索引整数(从 0 开始)。然后,映射回调的每个结果依次被等待,然后添加到数组中。
然而,当提供了 mapfn 且输入是同步可迭代对象(或不可迭代的类数组)时,输入中的每个值在被传递给 mapfn 之前都会被等待。(如果输入是异步可迭代对象,则输入中的值不被等待。)这符合 for await 的行为。
当未提供 mapfn 时,来自异步输入的每个产生的值不被等待,而来自同步输入的每个产生的值在添加到结果数组之前只被等待一次。这也符合 for await 的行为。
这意味着:
…不等价于:
…至少当 input 是异步可迭代对象时是这样。
这是因为,每当输入是产生 promise 项的异步可迭代对象时,Array.fromAsync(input) 不会解析这些 promise 项,但 Array.fromAsync(input, x => x) 会解析它们,因为 x => x 映射函数的结果被等待。
例如:
另请参见 issue #19。
this 参数
thisArg 是映射回调的 this 绑定接收值。默认情况下为 undefined。这些可选参数与 Array.from 的行为一致。将它们排除会让已经习惯 Array.from 的开发者感到意外。
错误
与其他基于 promise 的 API 一样,Array.fromAsync 将始终立即返回一个 promise。Array.fromAsync 永远不会同步抛出错误和召唤 Zalgo。
当 Array.fromAsync 的输入在创建其异步或同步迭代器时抛出错误,Array.fromAsync 返回的 promise 将以该错误拒绝。
当 Array.fromAsync 的输入是可迭代的,但输入的迭代器在迭代时抛出错误,Array.fromAsync 返回的 promise 将以该错误拒绝。
当 Array.fromAsync 的输入仅是同步的(即,输入不是异步可迭代对象),并且当输入的一个值是一个最终会拒绝或已经拒绝的 promise 时,迭代停止,Array.fromAsync 返回的 promise 将用第一个此类错误拒绝。
在这种情况下,Array.fromAsync 将仅当该拒绝不发生在迭代到达并等待它之前的微任务中时,才捕获并处理该第一个输入拒绝。
就像 for await 一样,当输入的 promises 的拒绝发生在 Array.fromAsync 的迭代到达这些 promises 之前,Array.fromAsync 将不捕获这些拒绝。
这是因为 —— 像 for await 一样 —— Array.fromAsync 惰性迭代其输入,并顺序等待每个产生的值。每当开发者需要将产生 promises 的同步输入转储到数组中时,开发者需要在 Array.fromAsync 和 Promise.all 之间谨慎选择,它们具有互补的控制流(请参见 § 同步可迭代输入)。
例如,当同步输入包含两个 promises,后者在前者 promise 解析之前拒绝,Array.fromAsync 将不捕获该拒绝,因为它惰性地到达拒绝的 promise 时,它已经拒绝了。
当 Array.fromAsync 的输入至少有一个值,并且当 Array.fromAsync 的映射回调在给定这些值中的任何一个时抛出错误,Array.fromAsync 返回的 promise 将用第一个此类错误拒绝。
当 Array.fromAsync 的输入为 null 或 undefined,或者当 Array.fromAsync 的映射回调既不是 undefined 也不可调用时,Array.fromAsync 返回的 promise 将用 TypeError 拒绝。
关闭同步可迭代对象?
Array.fromAsync 尝试尽可能匹配 for await 的行为。
以前,当 for await 产生一个被拒绝的 promise 时,它不会关闭同步可迭代对象。
旧代码示例
TC39 最近更改了 for await 在这里的行为。
在最新版本的语言中,
当异步包装器产生拒绝时,for await 现在将关闭同步迭代器(参见 tc39/ecma262#2600)。
所有 JavaScript 引擎都已经在更新到这种新行为。
Array.fromAsync 匹配 for await 的这种新行为。
两者都会在同步迭代器产生一个被拒绝的 promise 作为其下一个值时关闭任何给定的同步迭代器。
其他提案
与迭代器辅助方法的关系
iterator-helpers 和 async-iterator-helpers 提案定义了 Iterator.toArray 和 AsyncIterator.toArray。以下成对的行是等价的:
Iterator.toArray 与 Array.from 重叠,AsyncIterator.toArray 与 Array.fromAsync 重叠。这没关系:它们都可以共存。
[iterable-helpers 的共同发起人][tc39/proposal-iterator-helpers#156] 同意我们应该同时拥有两者,或者我们应该优先考虑 Array.fromAsync:“我记得为什么对于一个可构建的结构来说,消费一个可迭代对象比一个可迭代对象消费一个可构建协议更好。有时一次构建一个元素等同于一次构建[多个]元素,但有时这样构建可能会很慢,或者产生一个具有相同语义但性能特性不同的结构。”
[tc39/proposal-iterator-helpers#156]: https://github.com/tc39/proposal-iterator-helpers/issues/156. 协议
TypedArray.fromAsync、Set.fromAsync、Object.fromEntriesAsync 等
以下内置方法也类似于 Array.from:
我们将这些方法的任何异步版本推迟到未来的提案。 请参见 issue #8 和 proposal-setmap-offrom。
异步展开运算符
未来,标准化异步展开运算符(如 [ 0, await ...v ])可能会很有用。本提案将这一想法留给单独的提案。
记录和元组
record/tuple 提案提出了两种新的数据类型,其 API 分别类似于 Array 和 Object 的 API。Tuple 构造函数也可能需要一个 fromAsync 方法。Record 构造函数是否获得 fromEntriesAsync 方法将取决于 Object.fromEntriesAsync 是否也将在单独的提案中添加。
真实世界示例
仅对现状示例做了轻微格式更改。