Mastering Array.fromAsync: Async Iteration Made Easy in JavaScript
This article introduces the Stage‑3 ECMAScript proposal Array.fromAsync, explains why it’s needed for async iterables, shows its usage with async, sync, and array‑like inputs, provides real‑world examples, and offers a polyfill implementation for current environments.
Array.fromAsync is an ECMAScript proposal that has reached Stage‑3 and is likely to be included in the ECMAScript 2023 standard. This article gives a concise introduction.
Why Array.fromAsync Is Needed
ECMAScript 6 introduced
Array.from, which creates a shallow‑copy array from an array‑like or iterable object. For example:
<code>// Using an arrow function as the map function to
// manipulate the elements
Array.from([1, 2, 3], x => x + x);
// [2, 4, 6]
// Generate a sequence of numbers
// Since the array is initialized with `undefined` on each position,
// the value of `v` below will be `undefined`
Array.from({length: 5}, (v, i) => i);
// [0, 1, 2, 3, 4]
</code> Array.fromcan be seen as a method that iterates over a pseudo‑array or iterable and converts it to a real array:
<code>const arr = [];
for (const v of iterable) {
arr.push(v);
}
// This does the same thing.
const arr = Array.from(iterable);
</code>However, there is no equivalent for asynchronous iterables, which would be useful for dumping an async iterator into a single data structure, especially in unit tests or CLI scenarios.
<code>const arr = [];
for await (const v of asyncIterable) {
arr.push(v);
}
// We should add something that does the same thing.
const arr = await ??????????(asyncIterable);
</code>Just as
Array.fromis used with
for,
Array.fromAsyncis intended for
for awaitscenarios.
How to Use Array.fromAsync
Like
Array.from,
Array.fromAsyncaccepts three parameters:
items,
mapfn, and
thisArg.
itemsis required;
mapfnand
thisArgare optional.
itemscan be an async iterator, a sync iterator, or an array‑like object.
Async Iterator
Array.fromAsyncconverts an async iterator into a promise that resolves to a new array. It lazily iterates the input, awaiting each yielded value before adding it to the result.
<code>async function * asyncGen (n) {
for (let i = 0; i < n; i++)
yield i * 2;
}
// `arr` will be `[0, 2, 4, 6]`.
const arr = [];
for await (const v of asyncGen(4)) {
arr.push(v);
}
// This is equivalent.
const arr = await Array.fromAsync(asyncGen(4));
</code>Sync Iterator
If
itemsis a sync iterator, the return value is still a promise that resolves to an array. If the iterator yields promises, each promise is awaited before being added to the result.
<code>function * genPromises (n) {
for (let i = 0; i < n; i++)
yield Promise.resolve(i * 2);
}
// `arr` will be `[ 0, 2, 4, 6 ]`.
const arr = [];
for await (const v of genPromises(4)) {
arr.push(v);
}
// This is equivalent.
const arr = await Array.fromAsync(genPromises(4));
</code>Array‑like Object
For objects that have a
lengthproperty and indexed elements,
Array.fromAsyncalso returns a new array:
<code>const arrLike = {
length: 4,
0: Promise.resolve(0),
1: Promise.resolve(2),
2: Promise.resolve(4),
3: Promise.resolve(6),
}
// `arr` will be `[ 0, 2, 4, 6 ]`.
const arr = [];
for await (const v of Array.from(arrLike)) {
arr.push(v);
}
// This is equivalent.
const arr = await Array.fromAsync(arrLike);
</code>Real‑World Use Cases
In the following test case, data from a pipeline is collected into an array for assertions:
<code>async function toArray(items) {
const result = [];
for await (const item of items) {
result.push(item);
}
return result;
}
it('empty-pipeline', async () => {
const pipeline = new Pipeline();
const result = await toArray(
pipeline.execute(
[1, 2, 3, 4, 5]));
assert.deepStrictEqual(
result,
[1, 2, 3, 4, 5],
);
});
</code>Using
Array.fromAsyncsimplifies this to:
<code>it('empty-pipeline', async () => {
const pipeline = new Pipeline();
const result = await Array.fromAsync(
pipeline.execute(
[1, 2, 3, 4, 5]));
assert.deepStrictEqual(
result,
[1, 2, 3, 4, 5],
);
});
</code>Polyfill Implementation
Since
Array.fromAsyncis still a proposal, a polyfill is required for current environments. Below is a reference implementation:
<code>async function fromAsync (items, mapfn, thisArg) {
const itemsAreIterable = (
asyncIteratorSymbol in items ||
iteratorSymbol in items
);
if (itemsAreIterable) {
const result = isConstructor(this)
? new this
: IntrinsicArray(0);
let i = 0;
for await (const v of items) {
if (i > MAX_SAFE_INTEGER) {
throw TypeError(tooLongErrorMessage);
}
else if (mapfn) {
result[i] = await mapfn.call(thisArg, v, i);
}
else {
result[i] = v;
}
i ++;
}
result.length = i;
return result;
}
else {
// In this case, the items are assumed to be an array‑like object with a length property.
const { length } = items;
const result = isConstructor(this)
? new this(length)
: IntrinsicArray(length);
let i = 0;
while (i < length) {
if (i > MAX_SAFE_INTEGER) {
throw TypeError(tooLongErrorMessage);
}
const v = await items[i];
if (mapfn) {
result[i] = await mapfn.call(thisArg, v, i);
}
else {
result[i] = v;
}
i ++;
}
result.length = i;
return result;
}
};
</code>If you are interested, you can view the source code at github.com/es-shims/array-from-async .
KooFE Frontend Team
Follow the latest frontend updates
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.