[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"quiz-array-from-async-async-iterable":32,"quiz-article-array-from-async-async-iterable":48},[4,20],{"id":5,"slug":6,"name":7,"tagline":8,"description":9,"accentFrom":10,"accentTo":11,"icon":12,"defaultLocale":13,"locales":14,"features":16,"position":19},"019fe637-3d33-714b-b57f-23e163ffca0c","dev","Web Development","Build. Learn. Ship.","Practical courses, engineering-grade articles and open-source tools for people who ship.","violet-500","cyan-400","◇","en",[13,15],"fa",{"courses":17,"paths":17,"articles":17,"exams":18,"flashcards":18,"packages":17,"community":17,"certificates":17,"teams":17,"commerce":17},true,false,0,{"id":21,"slug":22,"name":23,"tagline":24,"description":25,"accentFrom":26,"accentTo":10,"icon":27,"defaultLocale":13,"locales":28,"features":30,"position":31},"019fe637-3dc2-754c-8657-0f175bfee7c6","lang","Languages","Learn a language the way you learn a codebase.","Structured paths, listening drills and spaced repetition that actually sticks.","amber-400","⌘",[13,15,29],"es",{"courses":17,"paths":17,"articles":18,"exams":18,"flashcards":18,"packages":18,"community":17,"certificates":18,"teams":18,"commerce":17},2,{"id":33,"slug":34,"kind":35,"title":36,"description":37,"config":38,"verticalId":5,"vertical":43,"course":40,"_count":44,"access":45,"attempts":47,"questionCount":39},"019fe776-ba13-721a-9b41-ecc76a7d836e","array-from-async-async-iterable","PRACTICE_QUIZ","Array.fromAsync — async iterables into arrays","Array.fromAsync collects an async iterable into a plain array with a single awaited call. These questions check what it accepts, how it differs from the broken spread workaround and from Promise.all, whether it processes values sequentially or concurrently, what the mapper argument does and whether it can return a promise, what browser versions shipped it, and when you should still reach for Promise.all instead.",{"questionCount":39,"timeLimitSec":40,"shuffleQuestions":18,"shuffleOptions":17,"negativeMarking":19,"passScorePct":41,"maxAttempts":40,"revealAnswers":42,"allowFlagging":18,"allowBacktracking":17},8,null,70,"IMMEDIATE",{"slug":6,"name":7},{"questions":39},{"allowed":17,"reason":46},"FREE",[],{"id":49,"slug":34,"title":50,"subtitle":40,"excerpt":51,"coverUrl":52,"locale":13,"readingMinutes":53,"publishedAt":54,"viewCount":55,"likeCount":19,"commentCount":19,"author":56,"vertical":61,"topic":62,"tags":65,"_count":77,"playground":79,"body":81,"bodyMd":244,"seo":245,"translationGroupId":249,"thread":250,"assessments":252,"translations":255,"quiz":257},"019fe660-de51-759d-935f-dbda9e7e38b5","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.","\u002Fmedia\u002Fcovers\u002Farray-from-async-async-iterable.png",4,"2026-08-05T08:42:50.920Z",180,{"id":57,"name":58,"username":59,"avatarUrl":40,"headline":60},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":63,"name":64},"javascript","JavaScript",[66,68,71,74],{"slug":63,"name":67,"color":40},"Javascript",{"slug":69,"name":70,"color":40},"webdev","Webdev",{"slug":72,"name":73,"color":40},"frontend","Frontend",{"slug":75,"name":76,"color":40},"async","Async",{"assessments":78},1,{"slug":34,"title":80},"Array.fromAsync — interactive playground",{"blocks":82,"version":78},[83,87,93,96,101,104,108,111,115,119,122,125,129,132,135,138,141,145,148,152,155,158,161,164,168,171,174,177,180,184,187,190,193,196,199,202,205,208,211,214,217,220,223,226,229,232,235],{"id":84,"html":85,"type":86},"b1","\u003Cp>When you have an async iterable — a \u003Ccode>ReadableStream\u003C\u002Fcode>, a generator that fetches paginated results, a database cursor — and you need its values in a plain array, you reach for a loop.\u003C\u002Fp>","paragraph",{"id":88,"code":89,"type":90,"language":91,"highlight":92},"b2","const results = [];\nfor await (const item of asyncSource) {\n  results.push(item);\n}","code","js",[],{"id":94,"html":95,"type":86},"b3","\u003Cp>That works. But it&#39;s four lines of ceremony for &quot;give me an array of everything this produces.&quot; \u003Ccode>Array.fromAsync\u003C\u002Fcode> is the one-liner that&#39;s been missing.\u003C\u002Fp>",{"id":97,"html":98,"text":99,"type":100,"level":31},"b4","The spread workaround doesn&#39;t work","The spread workaround doesn't work","heading",{"id":102,"html":103,"type":86},"b5","\u003Cp>If you&#39;ve tried \u003Ccode>[...asyncIterable]\u003C\u002Fcode>, you know it throws. Spread syntax works with synchronous iterables only. The \u003Ccode>await Promise.all([...asyncIterable])\u003C\u002Fcode> trick fails too — the spread happens before \u003Ccode>await\u003C\u002Fcode>, which means JavaScript tries to spread a synchronous iterator that doesn&#39;t exist on the async source.\u003C\u002Fp>",{"id":105,"code":106,"type":90,"language":91,"highlight":107},"b6","\u002F\u002F ❌ TypeError: asyncSource is not iterable\nconst results = [...asyncSource];\n\n\u002F\u002F ❌ Also fails — spread is sync, runs before await\nconst results = await Promise.all([...asyncSource]);",[],{"id":109,"html":110,"type":86},"b7","\u003Cp>The for-await loop is the correct fallback. But it&#39;s exactly the kind of boilerplate a standard library should absorb.\u003C\u002Fp>",{"id":112,"html":113,"text":114,"type":100,"level":31},"b8","\u003Ccode>Array.fromAsync\u003C\u002Fcode> in one call","Array.fromAsync in one call",{"id":116,"code":117,"type":90,"language":91,"highlight":118},"b9","const results = await Array.fromAsync(asyncSource);",[],{"id":120,"html":121,"type":86},"b10","\u003Cp>That&#39;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.\u003C\u002Fp>",{"id":123,"html":124,"type":86},"b11","\u003Cp>Like \u003Ccode>Array.from()\u003C\u002Fcode>, it accepts a mapping function as the second argument:\u003C\u002Fp>",{"id":126,"code":127,"type":90,"language":91,"highlight":128},"b12","const doubled = await Array.fromAsync(asyncNumbers, n => n * 2);",[],{"id":130,"html":131,"type":86},"b13","\u003Cp>The mapper runs after each value has been awaited. You can return a promise from the mapper too — \u003Ccode>Array.fromAsync\u003C\u002Fcode> awaits that as well before moving on.\u003C\u002Fp>",{"id":133,"html":134,"text":134,"type":100,"level":31},"b14","What sources it accepts",{"id":136,"html":137,"type":86},"b15","\u003Cp>\u003Ccode>Array.fromAsync\u003C\u002Fcode> accepts three kinds of input:\u003C\u002Fp>",{"id":139,"html":140,"type":86},"b16","\u003Cp>\u003Cstrong>Async iterables\u003C\u002Fstrong> — anything with a \u003Ccode>[Symbol.asyncIterator]()\u003C\u002Fcode> method. This is the main use case: generators, streams, cursors, any API that produces values lazily over time.\u003C\u002Fp>",{"id":142,"code":143,"type":90,"language":91,"highlight":144},"b17","async function* paginate(cursor) {\n  while (cursor.hasMore) {\n    const page = await cursor.fetch();\n    yield* page.items;\n    cursor.advance();\n  }\n}\n\nconst allItems = await Array.fromAsync(paginate(cursor));",[],{"id":146,"html":147,"type":86},"b18","\u003Cp>\u003Cstrong>Sync iterables\u003C\u002Fstrong> with an async mapper — this replaces the common \u003Ccode>await Promise.all(array.map(async fn))\u003C\u002Fcode> pattern, with one key difference explained below.\u003C\u002Fp>",{"id":149,"code":150,"type":90,"language":91,"highlight":151},"b19","const users = await Array.fromAsync([1, 2, 3], async id => {\n  const res = await fetch(`\u002Fapi\u002Fusers\u002F${id}`);\n  return res.json();\n});",[],{"id":153,"html":154,"type":86},"b20","\u003Cp>\u003Cstrong>Array-like objects\u003C\u002Fstrong> — objects with numeric indices and a \u003Ccode>length\u003C\u002Fcode> property, same as \u003Ccode>Array.from\u003C\u002Fcode>.\u003C\u002Fp>",{"id":156,"html":157,"text":157,"type":100,"level":31},"b21","Sequential, not concurrent",{"id":159,"html":160,"type":86},"b22","\u003Cp>This is the most important thing to understand about \u003Ccode>Array.fromAsync\u003C\u002Fcode>: it processes values one at a time, in order. It awaits each value fully before pulling the next.\u003C\u002Fp>",{"id":162,"html":163,"type":86},"b23","\u003Cp>This is different from \u003Ccode>Promise.all\u003C\u002Fcode>, which fires all promises concurrently and waits for all of them together.\u003C\u002Fp>",{"id":165,"code":166,"type":90,"language":91,"highlight":167},"b24","\u002F\u002F ✅ Use Promise.all when you want all fetches to run in parallel\nconst [a, b, c] = await Promise.all([fetchA(), fetchB(), fetchC()]);\n\n\u002F\u002F ✅ Use Array.fromAsync when the source is lazy — values produced one at a time\nconst results = await Array.fromAsync(asyncGenerator());",[],{"id":169,"html":170,"type":86},"b25","\u003Cp>When you pass a sync array with an async mapper, \u003Ccode>Array.fromAsync\u003C\u002Fcode> fetches item 1, awaits the result, stores it, then fetches item 2. There&#39;s no parallelism inside the pipeline. If you&#39;re mapping over a known array and want concurrent fetches, \u003Ccode>Promise.all\u003C\u002Fcode> is still the right tool.\u003C\u002Fp>",{"id":172,"html":173,"type":86},"b26","\u003Cp>The sequential behavior is intentional for lazy sources: a generator or stream doesn&#39;t know what to produce next until you ask — you can&#39;t fan out requests that haven&#39;t been decided yet.\u003C\u002Fp>",{"id":175,"html":176,"text":176,"type":100,"level":31},"b27","A practical pattern: async generator pipeline",{"id":178,"html":179,"type":86},"b28","\u003Cp>Async generators are where \u003Ccode>Array.fromAsync\u003C\u002Fcode> earns its place. Consider a paginated API client:\u003C\u002Fp>",{"id":181,"code":182,"type":90,"language":91,"highlight":183},"b29","async function* fetchPages(url) {\n  let next = url;\n  while (next) {\n    const res = await fetch(next);\n    const data = await res.json();\n    yield* data.items;\n    next = data.nextPage ?? null;\n  }\n}\n\n\u002F\u002F Before: manual loop\nconst all = [];\nfor await (const item of fetchPages('\u002Fapi\u002Fitems')) {\n  all.push(item);\n}\n\n\u002F\u002F After: one call\nconst all = await Array.fromAsync(fetchPages('\u002Fapi\u002Fitems'));",[],{"id":185,"html":186,"type":86},"b30","\u003Cp>Same result. The generator drives pagination and \u003Ccode>Array.fromAsync\u003C\u002Fcode> collects everything, stopping when the generator returns.\u003C\u002Fp>",{"id":188,"html":189,"text":189,"type":100,"level":31},"b31","Browser support",{"id":191,"html":192,"type":86},"b32","\u003Cp>\u003Ccode>Array.fromAsync\u003C\u002Fcode> is \u003Cstrong>Baseline 2024\u003C\u002Fstrong>: Chrome 121 (January 2024), Firefox 119 (October 2023), Safari 17.4 (March 2024), Node.js 22. For older targets, \u003Ccode>core-js\u003C\u002Fcode> 3.38+ includes a polyfill, and the manual for-await loop is always a valid fallback.\u003C\u002Fp>",{"id":194,"html":195,"type":86},"b33","\u003C!-- playground:start -->",{"id":197,"html":198,"text":198,"type":100,"level":31},"b34","🎮 Try it yourself",{"id":200,"html":201,"type":86},"b35","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fdaily-post-dev.netlify.app\u002Fposts\u002F2026-08-05-array-from-async-async-iterable\u002Fplayground\u002F\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":203,"html":204,"type":86},"b36","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":206,"html":207,"type":86},"b37","\u003C!-- playground:end -->",{"id":209,"html":210,"type":86},"b38","\u003C!-- quiz:start -->",{"id":212,"html":213,"text":213,"type":100,"level":31},"b39","🧠 Test yourself",{"id":215,"html":216,"type":86},"b40","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fdaily-post-dev.netlify.app\u002Fquiz\u002Ftake.html?post=2026-08-05-array-from-async-async-iterable\">Take the 8-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":218,"html":219,"type":86},"b41","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":221,"html":222,"type":86},"b42","\u003C!-- quiz:end -->",{"id":224,"html":225,"text":225,"type":100,"level":31},"b43","The takeaway",{"id":227,"html":228,"type":86},"b44","\u003Cp>Search your codebase for \u003Ccode>for await ... push\u003C\u002Fcode> patterns that end with the array being returned or used. Each one is a direct candidate for \u003Ccode>await Array.fromAsync(source)\u003C\u002Fcode>. When you&#39;re mapping an async function over a sync array and want sequential execution, \u003Ccode>Array.fromAsync(array, asyncMapper)\u003C\u002Fcode> replaces the manual loop. When you want concurrent execution over a known array, stick with \u003Ccode>await Promise.all(array.map(asyncMapper))\u003C\u002Fcode>. The distinction is sequential vs parallel — know which you need before reaching for either.\u003C\u002Fp>",{"id":230,"type":231},"b45","divider",{"id":233,"html":234,"type":86},"b46","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":236,"type":237,"items":238,"ordered":18},"b47","list",[239,240,241,242,243],"⭐ \u003Cstrong>GitHub\u003C\u002Fstrong> — follow me and star the projects: \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fparsajiravand\">github.com\u002Fparsajiravand\u003C\u002Fa>","💬 \u003Cstrong>Discord\u003C\u002Fstrong> — join the frontend best-practices community: \u003Ca href=\"https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ\">discord.gg\u002Fd9KRhuAwQ\u003C\u002Fa>","📸 \u003Cstrong>Instagram\u003C\u002Fstrong> — frontend best practices, daily: \u003Ca href=\"https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F\">@bestpractice___\u003C\u002Fa>","💼 \u003Cstrong>LinkedIn\u003C\u002Fstrong> — \u003Ca href=\"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fparsa-jiravand\u002F\">linkedin.com\u002Fin\u002Fparsa-jiravand\u003C\u002Fa>","✉️ \u003Cstrong>Email\u003C\u002Fstrong> (work &amp; contract inquiries): \u003Ca href=\"mailto:bestpractice2026@gmail.com\">bestpractice2026@gmail.com\u003C\u002Fa>","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.\n\n```js\nconst results = [];\nfor await (const item of asyncSource) {\n  results.push(item);\n}\n```\n\nThat 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.\n\n## The spread workaround doesn't work\n\nIf 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.\n\n```js\n\u002F\u002F ❌ TypeError: asyncSource is not iterable\nconst results = [...asyncSource];\n\n\u002F\u002F ❌ Also fails — spread is sync, runs before await\nconst results = await Promise.all([...asyncSource]);\n```\n\nThe for-await loop is the correct fallback. But it's exactly the kind of boilerplate a standard library should absorb.\n\n## `Array.fromAsync` in one call\n\n```js\nconst results = await Array.fromAsync(asyncSource);\n```\n\nThat'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.\n\nLike `Array.from()`, it accepts a mapping function as the second argument:\n\n```js\nconst doubled = await Array.fromAsync(asyncNumbers, n => n * 2);\n```\n\nThe 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.\n\n## What sources it accepts\n\n`Array.fromAsync` accepts three kinds of input:\n\n**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.\n\n```js\nasync function* paginate(cursor) {\n  while (cursor.hasMore) {\n    const page = await cursor.fetch();\n    yield* page.items;\n    cursor.advance();\n  }\n}\n\nconst allItems = await Array.fromAsync(paginate(cursor));\n```\n\n**Sync iterables** with an async mapper — this replaces the common `await Promise.all(array.map(async fn))` pattern, with one key difference explained below.\n\n```js\nconst users = await Array.fromAsync([1, 2, 3], async id => {\n  const res = await fetch(`\u002Fapi\u002Fusers\u002F${id}`);\n  return res.json();\n});\n```\n\n**Array-like objects** — objects with numeric indices and a `length` property, same as `Array.from`.\n\n## Sequential, not concurrent\n\nThis 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.\n\nThis is different from `Promise.all`, which fires all promises concurrently and waits for all of them together.\n\n```js\n\u002F\u002F ✅ Use Promise.all when you want all fetches to run in parallel\nconst [a, b, c] = await Promise.all([fetchA(), fetchB(), fetchC()]);\n\n\u002F\u002F ✅ Use Array.fromAsync when the source is lazy — values produced one at a time\nconst results = await Array.fromAsync(asyncGenerator());\n```\n\nWhen 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.\n\nThe 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.\n\n## A practical pattern: async generator pipeline\n\nAsync generators are where `Array.fromAsync` earns its place. Consider a paginated API client:\n\n```js\nasync function* fetchPages(url) {\n  let next = url;\n  while (next) {\n    const res = await fetch(next);\n    const data = await res.json();\n    yield* data.items;\n    next = data.nextPage ?? null;\n  }\n}\n\n\u002F\u002F Before: manual loop\nconst all = [];\nfor await (const item of fetchPages('\u002Fapi\u002Fitems')) {\n  all.push(item);\n}\n\n\u002F\u002F After: one call\nconst all = await Array.fromAsync(fetchPages('\u002Fapi\u002Fitems'));\n```\n\nSame result. The generator drives pagination and `Array.fromAsync` collects everything, stopping when the generator returns.\n\n## Browser support\n\n`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.\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fdaily-post-dev.netlify.app\u002Fposts\u002F2026-08-05-array-from-async-async-iterable\u002Fplayground\u002F)**\n\n_Runs right in your browser — poke at it and watch the concept react live._\n\n\u003C!-- playground:end -->\n\n\u003C!-- quiz:start -->\n\n## 🧠 Test yourself\n\nThink it clicked? **[Take the 8-question quiz →](https:\u002F\u002Fdaily-post-dev.netlify.app\u002Fquiz\u002Ftake.html?post=2026-08-05-array-from-async-async-iterable)**\n\n_Instant feedback, a hint on every question, and an explanation for each answer — right or wrong._\n\n\u003C!-- quiz:end -->\n\n## The takeaway\n\nSearch 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.\n\n---\n\n*Thanks for reading! Let's stay connected:*\n\n- ⭐ **GitHub** — follow me and star the projects: [github.com\u002Fparsajiravand](https:\u002F\u002Fgithub.com\u002Fparsajiravand)\n- 💬 **Discord** — join the frontend best-practices community: [discord.gg\u002Fd9KRhuAwQ](https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ)\n- 📸 **Instagram** — frontend best practices, daily: [@bestpractice___](https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F)\n- 💼 **LinkedIn** — [linkedin.com\u002Fin\u002Fparsa-jiravand](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fparsa-jiravand\u002F)\n- ✉️ **Email** (work & contract inquiries): [bestpractice2026@gmail.com](mailto:bestpractice2026@gmail.com)",{"title":246,"canonical":247,"description":248},"You're collecting async iterable results with a for-await loop. `Array","https:\u002F\u002Fbestpractic.org\u002Fblog\u002Farray-from-async-async-iterable","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 ca","019fe660-de51-759d-935f-dfe3f9c67b01",{"id":251,"locked":18},"019fe660-e549-71a3-9c93-1eb02c238b81",[253],{"id":33,"slug":34,"title":36,"_count":254},{"questions":39},[256],{"locale":13,"slug":34},{"id":33,"slug":34,"title":36,"_count":258,"questionCount":39},{"questions":39}]