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

21.2k views Asked by At

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

2

There are 2 answers

2
jfriend00 On BEST ANSWER

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

1
JoCh On

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.