Reputation: 1
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
Reputation: 385
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.
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
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
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