Array.fromAsync for JavaScript
ECMAScript Stage-1 Proposal. J. S. Choi, 2021.
- Specification available
- Polyfills:
Why an Array.fromAsync method
Since its standardization in JavaScript,
Array.from has become one of Array’s
most frequently used built-in methods.
However, no similar functionality exists for async iterators.
Such functionality would be useful for dumping the entirety of an async iterator into a single data structure, especially in unit tests or in command-line interfaces. (Several real-world examples are included in a following section.)
There is an it-all NPM library that performs only this task
and which gets about 50,000 weekly downloads daily.
This of course does not include any code
that uses ad-hoc for await–of loops with empty arrays:
const arr = [];
for await (const item of asyncItems) {
arr.push(item);
}
Further demonstrating the demand for such functionality, several Stack Overflow questions have been asked by various developers, asking how to convert async iterators to arrays.
Description
(A formal draft specification is available.)
Similarly to Array.from,
Array.fromAsync would be a static method
of the Array built-in class, with one required argument
and two optional arguments: (items, mapfn, thisArg).
But instead of converting an array-like object or iterable to an array, it converts an async iterable (or array-like object or iterable) to a promise that will resolve to an array.
async function * f () {
for (let i = 0; i < 4; i++)
yield i;
}
// Resolves to [0, 1, 2, 3].
await Array.fromAsync(f());
mapfn is an optional function to call on every item value.
(Unlike Array.from, mapfn may be an async function.
Whenever mapfn returns a promise, that promise will be awaited,
and the value it resolves to is what is added
to the final returned promise’s array.
If mapfn’s promise rejects,
then the final returned promise
will also reject with that error.)
thisArg is an optional value with which to call mapfn
(or undefined by default).
Like for await, when Array.fromAsync receives a sync-iterable object
(and that object is not async iterable),
then it creates a sync iterator for that object and adds its items to an array.
When any yielded item is a promise, then that promise will block the iteration
until it resolves to a value (in which case that value is what is added to the array)
or until it rejects with an error (in which case
the promise returned by Array.fromAsync itself will reject with that error).
Like Array.from, Array.fromAsync also works on non-iterable array-like objects
(i.e., objects with a length property and indexed elements).
As with sync-iterable objects, any element that is a promise must settle first,
and the value to which it resolves (if any) will be what is added to the resulting array.
Also like Array.from, Array.fromAsync is a generic factory method.
It does not require that its this value be the Array constructor,
and it can be transferred to or inherited by any other constructors
that may be called with a single numeric argument.
Other proposals
Object.fromEntriesAsync
In the future, a complementary method could be added to Object.
| Type | Sync method | Async method |
|---|---|---|
Array |
from |
fromAsync |
Object |
fromEntries |
fromEntriesAsync? |
It is uncertain whether Object.fromEntriesAsync
should be piggybacked onto this proposal
or left to a separate proposal.
Async spread operator
In the future, standardizing an async spread operator (like [ 0, await ...v ])
may be useful. This proposal leaves that idea to a separate proposal.
Iterator helpers
The iterator-helpers proposal puts forward, among other methods,
a toArray method for async iterators (as well as synchronous iterators).
We could consider Array.fromAsync to be redundant with toArray.
However, Array.from already exists,
and Array.fromAsync would parallel it.
If we had to choose between asyncIterator.toArray and Array.fromAsync,
we should prefer Array.fromAsync to asyncIterator.toArray
for its parallelism with what already exists.
In addition, the iterator.toArray method already would duplicate Array.from
for synchronous iterators.
We consider duplication with an Array method as okay anyway.
If duplication between syncIterator.toArray and Array.from is already okay,
then duplication between asyncIterator.toArray and Array.fromAsync should also be okay.
Records and tuples
The record/tuple proposal puts forward two new data types
with APIs that respectively resemble those of Array and Object.
The Tuple constructor, too, would probably need an fromAsync method.
Whether the Record constructor gets a fromEntriesAsync method
depends on whether Object gets fromEntriesAsync.
Set and Map
There is a proposal for Set.from and Map.from methods.
If this proposal is accepted before that proposal,
then that proposal could also add corresponding fromAsync methods.
Real-world examples
Only minor formatting changes have been made to the status-quo examples.
| Status quo | With binding |
|---|---|
|
|
From js-libp2p/test/content-routing/content-routing.node.js. |
|
|
|