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:

// 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]
Array.from

can be seen as a method that iterates over a pseudo‑array or iterable and converts it to a real array:

const arr = [];
for (const v of iterable) {
  arr.push(v);
}

// This does the same thing.
const arr = Array.from(iterable);

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.

const arr = [];
for await (const v of asyncIterable) {
  arr.push(v);
}

// We should add something that does the same thing.
const arr = await ??????????(asyncIterable);

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.

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));

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.

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));

Array‑like Object

For objects that have a length property and indexed elements, Array.fromAsync also returns a new array:

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);

Real‑World Use Cases

In the following test case, data from a pipeline is collected into an array for assertions:

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],
  );
});

Using Array.fromAsync simplifies this to:

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],
  );
});

Polyfill Implementation

Since Array.fromAsync is still a proposal, a polyfill is required for current environments. Below is a reference implementation:

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;
  }
};

If you are interested, you can view the source code at github.com/es-shims/array-from-async .

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

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

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.