Array.fromAsync is not a function: why it happens and how to fix it (Node, browsers, TypeScript)
TypeError: Array.fromAsync is not a function means your runtime predates Array.fromAsync (Node 22, Chrome 121, Firefox 115, Safari 16.4). How to check, upgrade, polyfill with core-js or a 10-line fallback, and fix the TypeScript lib error.
You wrote await Array.fromAsync(stream), it worked on your laptop, and then CI or a user's browser threw TypeError: Array.fromAsync is not a function. The cause is always the same: the JavaScript engine running your code is older than the feature. Array.fromAsync is a built-in, not syntax, so no bundler or transpiler setting will make it appear. You either run it on an engine that has it, or you add it yourself.
This post is the short, practical fix, in the same format as the Promise.withResolvers and Object.groupBy fixes. For the method itself (edge cases, error handling, performance) see the complete Array.fromAsync guide.
What the error looks like
The same problem shows up under a few different names depending on where you hit it:
- Node:
TypeError: Array.fromAsync is not a function, typically on Node 18 or Node 20. Both lines are End-of-Life now, but they are still common in Docker base images and old CI configs. - Jest: the same
TypeError, with or withoutjest-environment-jsdom. Jest runs on whatever Node is installed on the machine, so a jsdom or Babel setup does not add the method; the Node version does. Checknode -von the runner, not just locally. - Browsers:
TypeError: Array.fromAsync is not a functionin the console on Safari before 16.4, Chrome and Edge before 121, Firefox before 115. On Safari the message may readArray.fromAsync is not a function. (In 'Array.fromAsync(...)', 'Array.fromAsync' is undefined). - TypeScript:
error TS2339: Property 'fromAsync' does not exist on type 'ArrayConstructor'. This is a compile-time version of the same issue: yourlibsetting does not include the declaration. It is fixed intsconfig.json, not by upgrading Node.
Where Array.fromAsync is available
Array.fromAsync was a TC39 proposal for years before it reached Stage 4 at the May 2025 plenary; it is listed among the finished proposals for ECMAScript 2026. Engines shipped it earlier than the standard did. From MDN's compatibility data and caniuse:
- Chrome 121 and Edge 121 (January 2024), Opera 107, Samsung Internet 25.
- Firefox 115.
- Safari 16.4 on macOS and iOS.
- Node.js 22.0.0 (April 2024, V8 12.4). The Node 22 release announcement lists it explicitly alongside Set methods and iterator helpers. Node 20 and 18 never got it.
- Deno 1.38 and Bun 1.0, according to MDN's browser-compat-data.
So the rule of thumb: any runtime from 2024 onwards has it; anything older does not.
Step 1: confirm which runtime is throwing
Before changing anything, find out what is actually executing the code. In Node, print the version and V8 version; in any environment, feature-detect:
node -v
# v20.19.5 -> too old, no Array.fromAsync
# v22.x / v24.x / v26.x -> has it
node -p "process.versions.v8"
node -p "typeof Array.fromAsync"
# 'function' on 22+, 'undefined' on 20 and belowIf node -v is fine locally but the error happens in CI or production, the mismatch is almost always a .nvmrc, an engines field, a FROM node:20 line in a Dockerfile or a setup-node step pinned to an old major. Fix the pin, not the code.
Fix 1: upgrade the runtime (preferred)
If you control the runtime, upgrade. As of November 2026 the supported Node lines are 22, 24 and 26, all LTS; Node 26 entered LTS in October, as covered in which Node version to use after Node 26 LTS. Node 20 and 18 are End-of-Life and will not get security fixes, so moving off them solves more than this one error. Any of the three supported lines has Array.fromAsync.
In practice that is nvm install 24 && nvm use 24 (or fnm/volta), updating .nvmrc to match, changing FROM node:20-alpine to FROM node:24-alpine in the Dockerfile, and setting "engines": { "node": ">=22" } in package.json so the next person gets a warning instead of a mystery. For browsers you cannot upgrade for your users, so check your analytics. If Safari 15 or Chrome 120 still make up a share you care about, go to Fix 2.
Fix 2: polyfill it
Two maintained options. core-js patches the global Array so existing code keeps calling Array.fromAsync; import the single module once at your entry point, before anything that uses it:
// entry.js: install the global, then use it anywhere
import 'core-js/actual/array/from-async';
const rows = await Array.fromAsync(asyncRows());core-js also exposes it as core-js/stable/array/from-async, and core-js-pure/actual/array/from-async if you want a function import without touching globals. If you use @babel/preset-env with useBuiltIns: 'usage' and a targets list that includes old browsers, Babel injects this import for you; otherwise add it by hand.
The second option is the standalone array-from-async package on npm (BSD-3-Clause, maintained by the proposal's author and ljharb). It does not patch Array; it exports a function with the same (items, mapFn, thisArg) signature, so you call fromAsync(...) instead of Array.fromAsync(...). That is the right choice for a library, where modifying globals is bad manners.
One thing that does not work: Babel's preset-env or TypeScript's target on their own. Those rewrite syntax (async/await, optional chaining, class fields). Array.fromAsync is a method on a global object; transpilers leave it alone and the error survives the build.
Fix 3: a 10-line fallback with for await
If you would rather not add a dependency, the core behaviour is small. for await already handles both async iterables and sync iterables of promises, which covers almost every real call:
async function arrayFromAsync(items, mapFn, thisArg) {
const out = [];
let i = 0;
for await (const item of items) {
out.push(mapFn ? await mapFn.call(thisArg, item, i) : item);
i++;
}
return out;
}
if (typeof Array.fromAsync !== 'function') {
Array.fromAsync = arrayFromAsync;
}This fallback skips the spec's array-like branch (objects with a length but no iterator), and it will not pass test262. For a stream, an async generator, or an array of promises it behaves the same as the real thing: values are awaited one at a time, in order. Use it on the old runtime only; the typeof guard means engines that have the native version keep it.
Fix 4: the TypeScript error (TS2339)
Property 'fromAsync' does not exist on type 'ArrayConstructor' means the compiler's lib files do not declare the method. In TypeScript 7.0.2, the current release, fromAsync lives in lib.esnext.array.d.ts; there is no es2026 lib yet (the numbered libs stop at es2025), so you need esnext or the specific esnext.array entry:
// tsconfig.json
{
"compilerOptions": {
"target": "es2022",
"lib": ["es2025", "esnext.array", "dom"]
// or simply: "lib": ["esnext", "dom"]
}
}Two reminders. Setting lib only teaches the type checker that the method exists; it does not make it exist at runtime, so pair it with one of the fixes above if you still target old engines. And the declaration is typed as Promise<T[]>, so if you forget await you get a promise, not an array, and the next line fails with something like rows.map is not a function.
A quick refresher: what Array.fromAsync does
Array.fromAsync(items, mapFn?, thisArg?) is the async twin of Array.from. It accepts an async iterable, a sync iterable, or an array-like, and returns a promise that resolves to an array. Values yielded by a sync iterable are awaited before being added; if you pass a mapFn, its return value is awaited too. The obvious use is draining an async source without writing the loop yourself:
// 1. async iterable -> array
async function* pages(url) {
let next = url;
while (next) {
const res = await fetch(next);
const body = await res.json();
yield body.items;
next = body.nextUrl;
}
}
const allPages = await Array.fromAsync(pages('/api/users'));
// 2. with a mapping function (its result is awaited)
const users = await Array.fromAsync(ids, async (id) => {
const res = await fetch('/api/users/' + id);
return res.json();
});The second example is where people get surprised. Array.fromAsync awaits each value sequentially: it does not request the next element until the current one has settled. Promise.all(ids.map(fetchUser)) starts every request at once and waits for all of them. MDN's own timing demo shows the difference clearly (roughly 500 ms versus 100 ms for five 100 ms tasks). So reach for Array.fromAsync when you are consuming an async iterator, or when you deliberately want one-at-a-time ordering such as rate-limited calls; reach for Promise.all when you already have the inputs and want parallelism. Also note that with an async iterable that yields promises, the yielded promises are passed through as-is unless your mapFn awaits them.
Summary
Array.fromAsync is not a functionmeans the engine is older than Chrome/Edge 121, Firefox 115, Safari 16.4 or Node 22.- Check with
node -vandtypeof Array.fromAsync === 'function', and look for pins in.nvmrc,engines, Dockerfiles and CI. - Best fix: Node 22, 24 or 26, all LTS; 18 and 20 are End-of-Life.
- Otherwise:
import 'core-js/actual/array/from-async', thearray-from-asyncpackage, or thefor awaitfallback above. - TypeScript TS2339: add
esnext(oresnext.array) tolib. Transpiling thetargetdoes not help, because this is a built-in, not syntax.