← blog
JavaScriptAugust 5, 2026 · 4 min read

You're collecting async iterable results with a for-await loop. Array.fromAsync does it in one call.

A for-await loop that pushes into an array, or the `await Promise.all([...asyncIterable])` workaround that silently fails — `Array.fromAsync` replaces both with a single awaited call. It's Baseline 2024.

Parsa Jiravand · Frontend engineer · building bestpractic
You're collecting async iterable results with a for-await loop. `Array.fromAsync` does it in one call.

When you have an async iterable — a ReadableStream, a generator that fetches paginated results, a database cursor — and you need its values in a plain array, you reach for a loop.

JavaScript
1
2
3
4
const results = []; for await (const item of asyncSource) { results.push(item); }

That works. But it's four lines of ceremony for "give me an array of everything this produces." Array.fromAsync is the one-liner that's been missing.

If you've tried [...asyncIterable], you know it throws. Spread syntax works with synchronous iterables only. The await Promise.all([...asyncIterable]) trick fails too — the spread happens before await, which means JavaScript tries to spread a synchronous iterator that doesn't exist on the async source.

JavaScript
1
2
3
4
5
// ❌ TypeError: asyncSource is not iterable const results = [...asyncSource]; // ❌ Also fails — spread is sync, runs before await const results = await Promise.all([...asyncSource]);

The for-await loop is the correct fallback. But it's exactly the kind of boilerplate a standard library should absorb.

JavaScript
const results = await Array.fromAsync(asyncSource);

That's it. It pulls one value from the source, awaits it, stores it, then pulls the next — returning a fully-populated plain array when the source is exhausted.

Like Array.from(), it accepts a mapping function as the second argument:

JavaScript
const doubled = await Array.fromAsync(asyncNumbers, n => n * 2);

The mapper runs after each value has been awaited. You can return a promise from the mapper too — Array.fromAsync awaits that as well before moving on.

Array.fromAsync accepts three kinds of input:

Async iterables — anything with a [Symbol.asyncIterator]() method. This is the main use case: generators, streams, cursors, any API that produces values lazily over time.

JavaScript
1
2
3
4
5
6
7
8
9
async function* paginate(cursor) { while (cursor.hasMore) { const page = await cursor.fetch(); yield* page.items; cursor.advance(); } } const allItems = await Array.fromAsync(paginate(cursor));

Sync iterables with an async mapper — this replaces the common await Promise.all(array.map(async fn)) pattern, with one key difference explained below.

JavaScript
1
2
3
4
const users = await Array.fromAsync([1, 2, 3], async id => { const res = await fetch(`/api/users/${id}`); return res.json(); });

Array-like objects — objects with numeric indices and a length property, same as Array.from.

This is the most important thing to understand about Array.fromAsync: it processes values one at a time, in order. It awaits each value fully before pulling the next.

This is different from Promise.all, which fires all promises concurrently and waits for all of them together.

JavaScript
1
2
3
4
5
// ✅ Use Promise.all when you want all fetches to run in parallel const [a, b, c] = await Promise.all([fetchA(), fetchB(), fetchC()]); // ✅ Use Array.fromAsync when the source is lazy — values produced one at a time const results = await Array.fromAsync(asyncGenerator());

When you pass a sync array with an async mapper, Array.fromAsync fetches item 1, awaits the result, stores it, then fetches item 2. There's no parallelism inside the pipeline. If you're mapping over a known array and want concurrent fetches, Promise.all is still the right tool.

The sequential behavior is intentional for lazy sources: a generator or stream doesn't know what to produce next until you ask — you can't fan out requests that haven't been decided yet.

Async generators are where Array.fromAsync earns its place. Consider a paginated API client:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
async function* fetchPages(url) { let next = url; while (next) { const res = await fetch(next); const data = await res.json(); yield* data.items; next = data.nextPage ?? null; } } // Before: manual loop const all = []; for await (const item of fetchPages('/api/items')) { all.push(item); } // After: one call const all = await Array.fromAsync(fetchPages('/api/items'));

Same result. The generator drives pagination and Array.fromAsync collects everything, stopping when the generator returns.

Array.fromAsync is Baseline 2024: Chrome 121 (January 2024), Firefox 119 (October 2023), Safari 17.4 (March 2024), Node.js 22. For older targets, core-js 3.38+ includes a polyfill, and the manual for-await loop is always a valid fallback.

Runs right in your browser — poke at it and watch the concept react live.

Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.

Search your codebase for for await ... push patterns that end with the array being returned or used. Each one is a direct candidate for await Array.fromAsync(source). When you're mapping an async function over a sync array and want sequential execution, Array.fromAsync(array, asyncMapper) replaces the manual loop. When you want concurrent execution over a known array, stick with await Promise.all(array.map(asyncMapper)). The distinction is sequential vs parallel — know which you need before reaching for either.


Thanks for reading! Let's stay connected:

Originally published on dev.to