Question
How to Use async/await with Array.map in TypeScript
Question
Given the following TypeScript code:
const arr = [1, 2, 3, 4, 5];
const results: number[] = await arr.map(async (item): Promise<number> => {
await callAsynchronousOperation(item);
return item + 1;
});
This produces the error:
TS2322: Type 'Promise<number>[]' is not assignable to type 'number[]'.
Type 'Promise<number>' is not assignable to type 'number'.
How can this be fixed? How do async/await and Array.map() work together correctly in TypeScript?
Short Answer
By the end of this page, you will understand why Array.map() does not automatically wait for async callbacks, why it returns an array of promises, and how to correctly combine it with Promise.all() in TypeScript to get the final resolved values.
Concept
Array.map() is a synchronous array method. Its job is simple: take each item in an array, run a callback, and return a new array containing the callback results.
When the callback is marked async, it always returns a promise. That means map() does not produce number[] anymore. It produces Promise<number>[].
So this code:
arr.map(async (item) => item + 1)
returns something like:
[Promise<number>, Promise<number>, Promise<number>, ...]
That is why TypeScript complains. You are trying to assign an array of promises to a variable typed as an array of numbers.
To turn Promise<number>[] into number[], you need Promise.all().
Promise.all() takes an array of promises, waits for all of them to finish, and gives back a single promise containing an array of resolved values.
Mental Model
Think of Array.map() as a factory conveyor belt.
- A normal callback puts finished products on the belt.
- An
asynccallback puts claim tickets on the belt instead of finished products.
Each claim ticket says, "A result will be ready later."
So after map(), you do not have the final products. You have tickets: Promise[].
Promise.all() is the collection desk that waits until every product is ready, then hands you the complete box of real results.
map()= create one result per inputasync= each result is promised laterPromise.all()= wait for all promised results
Syntax and Examples
Core syntax
const promises = arr.map(async (item) => {
return someAsyncWork(item);
});
const results = await Promise.all(promises);
A shorter version:
const results = await Promise.all(
arr.map(async (item) => {
await someAsyncWork(item);
return item + 1;
})
);
Correct version of your example
const arr = [1, 2, 3, 4, 5];
const results: number[] = await Promise.all(
arr.map(async (item): Promise<number> => {
(item);
item + ;
})
);
Step by Step Execution
Consider this code:
async function addOneLater(n: number): Promise<number> {
return n + 1;
}
async function run() {
const arr = [1, 2, 3];
const results = await Promise.all(
arr.map(async (item) => {
const value = await addOneLater(item);
return value;
})
);
console.log(results);
}
Step by step:
arris[1, 2, 3].map()starts running the callback for each item.- For
1, the callback returns aPromise<number>.
Real World Use Cases
Fetching multiple API resources
const userIds = [1, 2, 3];
const users = await Promise.all(
userIds.map(async (id) => {
const response = await fetch(`/api/users/${id}`);
return response.json();
})
);
Reading multiple files
const fileNames = ["a.txt", "b.txt", "c.txt"];
const contents = await Promise.all(
fileNames.map(async (name) => {
return readFile(name, "utf8");
})
);
Running validation checks
const emails = ["a@test.com", "b@test.com"];
const results = .(
emails.( (email) => {
(email);
})
);
Real Codebase Usage
In real projects, developers often use map() with async work in a few common patterns.
1. Parallel processing with Promise.all()
Use this when tasks are independent and can run at the same time.
const enrichedUsers = await Promise.all(
users.map(async (user) => ({
...user,
profile: await fetchProfile(user.id),
}))
);
2. Guard clauses before async work
const results = await Promise.all(
items.map(async (item) => {
if (!item.enabled) {
return null;
}
return processItem(item);
})
);
3. Validation before mapping
if (items.length === 0) {
[];
}
output = .(items.(saveItem));
Common Mistakes
1. Awaiting map() directly
Broken code:
const results = await arr.map(async (item) => item + 1);
Why it is wrong:
map()returns an array immediatelyawaiton that array does not resolve the inner promises into values
Fix:
const results = await Promise.all(
arr.map(async (item) => item + 1)
);
2. Expecting async callbacks to return plain values
Broken code:
const values: number[] = arr.map(async (item) => item + 1);
Why it is wrong:
- an
asyncfunction always returns
Comparisons
| Pattern | What it returns | Waits for async work? | Best use |
|---|---|---|---|
arr.map(fn) with sync callback | T[] | No need | Simple transformation |
arr.map(async fn) | Promise<T>[] | No | Start async work for each item |
await Promise.all(arr.map(async fn)) | T[] | Yes | Parallel async transformation |
for...of with await | Final values one by one | Yes, sequentially |
Cheat Sheet
// Wrong
const results = await arr.map(async (item) => item + 1);
// Right
const results = await Promise.all(
arr.map(async (item) => item + 1)
);
Rules
async functionalways returnsPromise<T>Array.map()is synchronousmap(async ...)returnsPromise<T>[]Promise.all(Promise<T>[])returnsPromise<T[]>await Promise.all(...)givesT[]
Common patterns
const results = await .(items.(doAsyncWork));
FAQ
Why does Array.map() return Promise[] with an async callback?
Because an async function always returns a promise. map() collects those return values as-is.
Can I use await directly on arr.map(...)?
No. arr.map(...) returns an array, not a single promise that resolves all inner results.
What is the correct way to use async with map()?
Use Promise.all() around the map() result:
const results = await Promise.all(arr.map(asyncFn));
Is Promise.all() parallel?
It starts all mapped async operations without waiting for one another, so they can run concurrently.
What if one mapped promise fails?
Promise.all() rejects immediately with that error.
Mini Project
Description
Build a small TypeScript utility that processes a list of product IDs asynchronously and returns updated prices. This demonstrates the exact pattern of using Array.map() with async callbacks and then resolving everything with Promise.all().
Goal
Create a function that asynchronously transforms an array of numbers into a new array of processed numbers.
Requirements
- Create an async function that simulates loading a product price by ID.
- Use
Array.map()to process each ID. - Use
Promise.all()to wait for all async operations. - Return a final
number[]. - Log the result to verify the output.
Keep learning
Related questions
@Directive vs @Component in Angular: Differences, Use Cases, and When to Use Each
Learn the difference between @Directive and @Component in Angular, including use cases, examples, and when to choose each.
Angular (change) vs (ngModelChange): What’s the Difference?
Learn the difference between Angular (change) and (ngModelChange), when each fires, and which one to use in forms and inputs.
Angular Dependency Injection: Fix "Can't Resolve All Parameters for Component" Errors
Learn why Angular shows "Can't resolve all parameters for component" and how to fix service injection issues in components.