Frontend Development 9 min read

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.

KooFE Frontend Team
KooFE Frontend Team
KooFE Frontend Team
Mastering Array.fromAsync: Async Iteration Made Easy in JavaScript

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

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

is used with

for

,

Array.fromAsync

is intended for

for await

scenarios.

How to Use Array.fromAsync

Like

Array.from

,

Array.fromAsync

accepts three parameters:

items

,

mapfn

, and

thisArg

.

items

is required;

mapfn

and

thisArg

are optional.

items

can be an async iterator, a sync iterator, or an array‑like object.

Async Iterator

Array.fromAsync

converts 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

items

is 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

length

property and indexed elements,

Array.fromAsync

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

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

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

JavaScriptpolyfillasync iterationArray.fromAsync
KooFE Frontend Team
Written by

KooFE Frontend Team

Follow the latest frontend updates

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.