0

Lets say that I have this code:

const array = [1, 2, 3]

let counter = 0

array.map(async (item) => {
  console.log(await item, ++counter)
  console.log(await item, ++counter)
})

the expected output would be

1, 1
1, 2
2, 3
2, 4
3, 5
3, 6

but what I am getting is this

1, 1
2, 2
3, 3
1, 4
2, 5
3, 6

it seems like the first await call is running first for the whole array, then the second one in being run, why is this happening?

17
  • Why do you expect the initial output? Commented May 7, 2021 at 13:47
  • 1
    And why would you need to await a number? Commented May 7, 2021 at 13:48
  • it is just part of a bigger problem simplified @Pointy Commented May 7, 2021 at 13:48
  • 1
    Async functions pause at await. That's the entirety of the explanation about your question. But is it really useful to solve your problem? You most likely shouldn't be using an async in a .map(). Unless you put that in a Promise.all() Commented May 7, 2021 at 13:56
  • 1
    @Keith, thank you I will sure read that. Commented May 7, 2021 at 14:04

3 Answers 3

2

Here's a (hopefully) better illustration. The thing is, map doesn't wait for its callback ("mapper") to complete. It just fires it for each element. So, when map is done, we end up with N mappers hanging around, each of them waiting for await to complete, because await, even if used with a non-promise, still means waiting, namely, waiting for the current "execution context" to exit.

const array = ['A', 'B', 'C']

let counter

function item(arg) {
    console.log('    CALLED item', arg, ' => ', counter++)
}

console.log('SYNC: before map')
counter = 0
array.map(async (x) => {
    console.log('  begin SYNC mapper', x)
    item(x);
    console.log('    1st item of', x, 'done')
    item(x);
    console.log('  end SYNC mapper', x)
})
console.log('after map')

console.log('--------------------------')

console.log('ASYNC: before map')
counter = 0
array.map(async (x) => {
    console.log('  begin ASYNC mapper', x)
    await item(x);
    console.log('    1st item of', x, 'done')
    await item(x);
    console.log('  end ASYNC mapper', x)
})
console.log('after map')

console.log('execution context ended')
.as-console-wrapper { max-height: 100% !important; top: 0; }

Sign up to request clarification or add additional context in comments.

Comments

1

Array methods like map, forEach etc are not await-aware. In other words, they would execute the callback function regardless of the await keywords used inside them.

Here is a test:

const array = [1, 2, 3]

array.map(async (item) => {
  console.log('Immediate Item: ', item);
  console.log('Awaited item', await Promise.resolve(item));
})

You can see from the results that all 3 "immediate items" are loged out first and then comes the "awaited" items.

If you need a loop that respects async/await, use for-of loop.

async function main() {

  const array = [1, 2, 3]

  for (let item of array) {
          console.log('Immediate Item: ', item);
          console.log('Awaited item', await Promise.resolve(item));
    }

}

main();

Comments

-1

Besides the asynchronous part

Don't use Array.map as an imperative loop. Use Array.forEach instead.

  • Array.map transforms each value (in any order (!)).
  • Array.forEach calls each value in sequential order.

Practically speaking, I guess you could assume sequential execution for Array.map, but it is bad practice to do that.

The asynchronous part

You are expecting Array.map to wait for the two console.log statements to finish, before calling the next item. This is neither true for Array.map or Array.forEach, because though the code might look sequential, it is asynchronous. And in Jav

If you want to execute asynchronous functions in sequence, there are basically two ways of doing it:

Use for loop

(async () => {
  for(var i=0; i<array.length; i++) {
    console.log(await array[i], ++counter)
    console.log(await array[i], ++counter)
  }
})()

Use Array.reduce

array.reduce(async (p, item) => {
  await p
  console.log(await item, ++counter)
  console.log(await item, ++counter)
}, Promise.resolve())

1 Comment

It's maybe worth pointing out the Array.reduce, creates something called a Promise chain,. Were as the for loop doesn't, for very large arrays the for loop would be better, and is more flexible as break / continue also work as expected.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.