Async Await map not awaiting async function to complete inside map function before mapping next item
Asked Answered
S

2

21

I have an array that I am mapping, inside the map function I am calling an asynchronous function, that is performing an asynchronous request returning a promise using request-promise.

I am expecting the first item of the array be mapped, perform the request and then the second item repeats the same process. But that's not what is happening in this instance.

This is my function;

const fn = async() => {
  const array = [0, 1, 2];
  console.log('begin');
  await Promise.all(array.map(item => anAsyncFunction(item)));
  console.log('finished');
  return;
}

anAsyncFunction is as follows;

const anAsyncFunction = async item => {
  console.log(`looping ${item}`);
  const awaitingRequest = await functionWithPromise(item);
  console.log(`finished looping ${item}`);
  return awaitingRequest;
}

And functionWithPromise where the request is made

const functionWithPromise = async (item) => {
  console.log(`performing request for ${item}`);
  return Promise.resolve(await request(`https://www.google.com/`).then(() => {
    console.log(`finished performing request for ${item}`);
    return item;
  }));
}

From the console logs I get;

begin
looping 0
performing request for 0
looping 1
performing request for 1
looping 2
performing request for 2
finished performing request for 0
finished looping 0
finished performing request for 1
finished looping 1
finished performing request for 2
finished looping 2
finished

However, what I want is

begin
looping 0
performing request for 0
finished performing request for 0
finished looping 0
looping 1
performing request for 1
finished performing request for 1
finished looping 1
looping 2
performing request for 2
finished performing request for 2
finished looping 2
finished

I'd normally be fine with this pattern but I seem to be getting some invalid body from the request call as I may be making too many at once.

Is there a better method for what I am trying to achieve

Saundrasaunter answered 24/11, 2020 at 0:32 Comment(1)
you can't wait for the previous promise to finish inside .map ... use a for loop, or a well constructed reduce loopSebastien
W
55

.map() is not async or promise aware. It just dutifully takes the value you return from its callback and stuffs it in the result array. Even though it's a promise in your case, it still just keeps on going, not waiting for that promise. And, there is nothing you can do in that regard to change the .map() behavior. That's just the way it works.

Instead, use a for loop and then await your async function inside the loop and that will suspend the loop.


Your structure:

await Promise.all(array.map(item => anAsyncFunction(item)));

is running all the anAsyncFunction() calls in parallel and then waiting for all of them to finish.


To run them sequentially, use a for loop and await the individual function call:

const fn = async() => {
  const array = [0, 1, 2];
  console.log('begin');
  for (let item of array) {
      await anAsyncFunction(item);
  }
  console.log('finished');
  return;
}

This is an important thing to know that none of the array iteration methods are async aware. That includes .map(), .filter(), .forEach(), etc... So, if you want to await something inside the loop in order to sequence your async operations, then use a regular for loop which is async aware and will pause the loop.

Wallah answered 24/11, 2020 at 0:49 Comment(2)
The fact that a simple for loop is async aware but not the other iterable methods like map, forEach etc really catches people off guard!Yearlong
@Yearlong - I don't even know if it was considered when promises where officially added to the language, but there would have been a serious compatibility issue if you suddenly changed the behavior of .forEach() or .map() to start looking at the return value from their callback when they previously didn't. Whereas for loops gained promise support from await which did not cause a backward compatibility issue. So, the progression is understandable even if not how you might have designed it if promises existed from the very beginning.Wallah
T
1

You can try replacing this line:

await Promise.all(array.map(item => anAsyncFunction(item)));

with:

await Promise.all(array.map(async(item) => await anAsyncFunction(item)));

Should work more nodejs way than for loop alternative, only forEach is to ban in this case.

Tolerance answered 9/4, 2022 at 18:52 Comment(1)
Please use proper code formatting.Rebellious

© 2022 - 2024 — McMap. All rights reserved.