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. |
|
|
|