Given the following code:
var arr = [1,2,3,4,5];
var results: number[] = await arr.map(async (item): Promise<number> => {
await callAsynchronousOperation(item);
return item + 1;
});
which produces the following error:
TS2322: Type 'Promise<number>[]' is not assignable to type 'number[]'. Type 'Promise<number> is not assignable to type 'number'.
How can I fix it? How can I make async await and Array.map work together?
11 Answers 11
The problem here is that you are trying to await an array of promises rather than a Promise. This doesn't do what you expect.
When the object passed to await is not a Promise, await simply returns the value as-is immediately instead of trying to resolve it. So since you passed await an array (of Promise objects) here instead of a Promise, the value returned by await is simply that array, which is of type Promise<number>[].
What you probably want to do is call Promise.all on the array returned by map in order to convert it to a single Promise before awaiting it.
According to the MDN docs for Promise.all:
The
Promise.all(iterable)method returns a promise that resolves when all of the promises in the iterable argument have resolved, or rejects with the reason of the first passed promise that rejects.
So in your case:
var arr = [1, 2, 3, 4, 5];
var results: number[] = await Promise.all(arr.map(async (item): Promise<number> => {
await callAsynchronousOperation(item);
return item + 1;
}));
This will resolve the specific error you are encountering here.
Depending on exactly what it is you're trying to do you may also consider using Promise.allSettled, Promise.any, or Promise.race instead of Promise.all, though in most situations (almost certainly including this one) Promise.all will be the one you want.
4 Comments
: colons mean?callAsynchronousOperation(item); with and without await inside the async map function?await the function will wait for the asynchronous operation to complete (or fail) before continuing, otherwise it'll just immediately continue without waiting.This is simplest way to do it.
await Promise.all(
arr.map(async (element) => {
....
})
)
3 Comments
await expressions are only allowed within async functions how does this answer work?useEffectSolution below to properly use async await and Array.map together. Process all elements of the array in parallel, asynchronously AND preserve the order:
const arr = [1, 2, 3, 4, 5, 6, 7, 8];
const randomDelay = () => new Promise(resolve => setTimeout(resolve, Math.random() * 1000));
const calc = async n => {
await randomDelay();
return n * 2;
};
const asyncFunc = async() => {
const unresolvedPromises = arr.map(calc);
const results = await Promise.all(unresolvedPromises);
document.write(results);
};
document.write('calculating...');
asyncFunc();
Also codepen.
Notice we only "await" for Promise.all. We call calc without "await" multiple times, and we collect an array of unresolved promises right away. Then Promise.all waits for resolution of all of them and returns an array with the resolved values in order.
2 Comments
There's another solution for it if you are not using native Promises but Bluebird.
You could also try using Promise.map(), mixing the array.map and Promise.all
In you case:
var arr = [1,2,3,4,5];
var results: number[] = await Promise.map(arr, async (item): Promise<number> => {
await callAsynchronousOperation(item);
return item + 1;
});
5 Comments
Promise.mapSeries or Promise.each are sequencial, Promise.map starts them all at once.Promise from Bluebird.You can use:
for await (let resolvedPromise of arrayOfPromises) {
console.log(resolvedPromise)
}
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of
If you wish to use Promise.all() instead you can go for Promise.allSettled()
So you can have better control over rejected promises.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled
1 Comment
for await ... of has no control over rejected promises at all.If you map to an array of Promises, you can then resolve them all to an array of numbers. See Promise.all.
Comments
I'd recommend using Promise.all as mentioned above, but if you really feel like avoiding that approach, you can do a for or any other loop:
const arr = [1,2,3,4,5];
let resultingArr = [];
for (let i in arr){
await callAsynchronousOperation(i);
resultingArr.push(i + 1)
}
FYI: If you want to iterate over items of an array, rather than indices (@ralfoide 's comment), use of instead of in inside let i in arr statement.
2 Comments
A solution using modern-async's map():
import { map } from 'modern-async'
...
const result = await map(myArray, async (v) => {
...
})
The advantage of using that library is that you can control the concurrency using mapLimit() or mapSeries().
Comments
I had a task on BE side to find all entities from a repo, and to add a new property url and to return to controller layer. This is how I achieved it (thanks to Ajedi32's response):
async findAll(): Promise<ImageResponse[]> {
const images = await this.imageRepository.find(); // This is an array of type Image (DB entity)
const host = this.request.get('host');
const mappedImages = await Promise.all(images.map(image => ({...image, url: `http://${host}/images/${image.id}`}))); // This is an array of type Object
return plainToClass(ImageResponse, mappedImages); // Result is an array of type ImageResponse
}
Note: Image (entity) doesn't have property url, but ImageResponse - has
Comments
By the way, note that you can easily create an extension to make it cleaner, and use it like this:
var arr = [1, 2, 3, 4, 5];
var results = await arr.mapAsync(async (e) => {
return await callAsynchronousOperation(e);
});
You just need to create extensions.ts file:
export {};
declare global {
interface Array<T> {
mapAsync<U>(mapFn: (value: T, index: number, array: T[]) => Promise<U>): Promise<U[]>;
}
if (!Array.prototype.mapAsync) {
Array.prototype.mapAsync = async function <U, T>(
mapFn: (value: T, index: number, array: T[]) => Promise<U>
): Promise<U[]> {
return await Promise.all(
(this as T[]).map(async (v, i, a): Promise<U> => {
return await mapFn(v, i, a);
})
);
};
}
Comments
There is a new way to do this using Array.fromAsync:
try {
result = await Array.fromAsync( array_of_promises )
}
catch (e) {
console.log("Caught rejected promise", e);
}
It also supports array-like objects
Array.fromAsync({
length: 3,
0: Promise.resolve(1),
1: Promise.resolve(2),
2: Promise.resolve(3),
}).then((array) => console.log(array));
// [1, 2, 3]
Check docs here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/fromAsync
Please beware as it was explained in the discussion below - it will only work with resolved promises and if promise is rejected there will be an exception which must be caught.
11 Comments
Array.fromAsync is meant to be used with async iterators, not with arrays of promises. It will eventually lead to unhandled promise rejections. See this question for the similar for await ... of case.await Array.fromAsync( await Promise.all(array_of__promises) )await Promise.all(...) already returns an array of values, it makes no sense to pass that into Array.fromAsync afterwards - just use only Promise.all. Regarding for await ... of, the problem described there applies equally to fromAsync as the async iteration is exactly what it does internally.Explore related questions
See similar questions with these tags.
arr.map()is synchronous and does not return a promise.map, which expects a synchronous one, and expect it to work.async, you're making that function return a promise. So of course, a map of async returns an array of promises :)