571

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?

6
  • 10
    Why are you trying to make a synchronous operation into an async operation? arr.map() is synchronous and does not return a promise.
    – jfriend00
    Commented Oct 19, 2016 at 19:45
  • 4
    You can't send an asynchronous operation to a function, like map, which expects a synchronous one, and expect it to work. Commented Oct 19, 2016 at 19:46
  • 5
    @jfriend00 I have many await statements in the inner function. It's actually a long function and I just simplified it to make it readable. I've added now an await call to make it clearer why it should be async.
    – Alon
    Commented Oct 19, 2016 at 20:05
  • 2
    You need to await something that returns a promise, not something that returns an array.
    – jfriend00
    Commented Oct 19, 2016 at 20:15
  • 5
    One useful thing to realise is that every time you mark a function as async, you're making that function return a promise. So of course, a map of async returns an array of promises :) Commented Oct 29, 2018 at 6:52

10 Answers 10

1168

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
  • 7
    What do the : colons mean?
    – Daniel
    Commented Oct 13, 2017 at 23:20
  • 31
    @DanielPendergast It's for type annotations in TypeScript.
    – Ajedi32
    Commented Oct 13, 2017 at 23:28
  • 1
    What is the difference between calling callAsynchronousOperation(item); with and without await inside the async map function?
    – nerdizzle
    Commented Mar 27, 2020 at 13:08
  • 2
    @nerdizzle That sounds like a good candidate for another question. Basically though, with await the function will wait for the asynchronous operation to complete (or fail) before continuing, otherwise it'll just immediately continue without waiting.
    – Ajedi32
    Commented Mar 27, 2020 at 14:20
99

This is simplest way to do it.

await Promise.all(
    arr.map(async (element) => {
        ....
    })
)
3
  • I keep getting await expressions are only allowed within async functions how does this answer work? Commented Jan 10 at 21:49
  • @TravisHeeter the function in which you are writing this await expression should be a async function.
    – ajitspyd
    Commented Jan 17 at 14:29
  • Note that you can't do this inside react useEffect
    – Pavindu
    Commented May 20 at 9:15
69

Solution 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
  • 4
    Over the years of using Javascript, I have always had issues with mapping over arrays without messing up the order due to the async nature of js. I have used promised.all but never thought that it resolves them as is. This answer literally changed how I write maps and other things now. God bless.
    – sakib11
    Commented Aug 14, 2020 at 19:12
  • 2
    Thanks for this solution, I found it much more eloquent and readable than the others provided.
    – S..
    Commented Dec 17, 2021 at 16:40
23

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
  • 3
    It is different - it doesn't run all operations in parallel, but rather executes them in sequence. Commented Nov 15, 2017 at 3:02
  • 5
    @AndreyTserkus Promise.mapSeries or Promise.each are sequencial, Promise.map starts them all at once.
    – Kiechlus
    Commented Nov 30, 2017 at 15:46
  • 1
    @AndreyTserkus you can run all or some operations in parallel by providing concurrency option.
    – user659682
    Commented Jan 18, 2018 at 21:26
  • 41
    It's worth mentioning that it's not a vanilla JS.
    – Michal
    Commented Feb 24, 2018 at 13:46
  • I missed the fact that you are talking about Bluebird when I first read this answer. It would be great to update the code of your answer to explicitly import Promise from Bluebird.
    – SunnyPro
    Commented Jun 21, 2023 at 17:50
16

If you map to an array of Promises, you can then resolve them all to an array of numbers. See Promise.all.

15

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

5

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
  • 14
    The Promise.all will be async for each element of the array. This will be a sync, it have to wait to finish one element in order to start the next one. Commented Mar 1, 2018 at 15:38
  • 5
    For those trying this approach, note that for..of is the proper way to iterate an array content, whereas for..in iterates over the indices.
    – ralfoide
    Commented Feb 14, 2020 at 2:46
4

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().

1

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);
      })
    );
  };
}
0

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.