GamesMan
GamesMan

Reputation: 1

using async await with map does not work as expected

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?

Upvotes: 0

Views: 1678

Answers (3)

Mal
Mal

Reputation: 385

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

Upvotes: -1

Charlie
Charlie

Reputation: 23798

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

Upvotes: 1

georg
georg

Reputation: 214959

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

Upvotes: 2

Related Questions