Have you ever tried using the await inside forEach?
[].forEach(async(item) => {
await doSomethingWith(item)
})
Well I have, when I didn't know much about async/await and forEach. But didn't have the expected result. To understand this, we must first understand how async/await works.
How async/await works:
async/await
is the another way of doing .then
on the Promises. So whenever an async function reaches the statement where await is used and Promise is yet to be resovled, that function is then runs separately from the parent function, unless we use await on the child function. Let me give you an example:
const functionA = async () => {
console.log("Starting Function A");
await new Promise((resolve) => {
setTimeout(resolve, 300);
});
console.log("Ending Function A");
};
const myFunction = () => {
console.log("1");
functionA();
console.log("2");
};
myFunction();
The above code logs:
1
Starting Function A
2
Ending Function A
You see, first 1
is logged, and then we go inside functionA, logs Starting Function A
and as soon as we hit await
keyword, functionA
runs separately and myFunction
moves on and logs 2
and finally when our promise resolves in 300 ms, we get the log of Ending Function A
.
Lets give it another try with using await on functionA:
const functionA = async () => {
console.log("Starting Function A");
await new Promise((resolve) => {
setTimeout(resolve, 300);
});
console.log("Ending Function A");
};
const myFunction = async () => {
console.log("1");
await functionA();
console.log("2");
};
myFunction();
Now we get the logs:
1
Starting Function A
Ending Function A
2
In this case, we must complete the execution of functionA to move on. But you notice that out myFunction
also becomes async. So it will also runs separately from parent function or global scope.
Let's log another thing just after calling myFunction
:
const functionA = async () => {
console.log("Starting Function A");
await new Promise((resolve) => {
setTimeout(resolve, 300);
});
console.log("Ending Function A");
};
const myFunction = async () => {
console.log("1");
await functionA();
console.log("2");
};
myFunction();
console.log("3");
Can you guess what will be logged?
Here is our logs:
1
Starting Function A
3
Ending Function A
2
You see, our code runs synchronously until we hit the await statement where Promise is yet to be resolved. So when we called myFunction
, our code ran synchronously and logged 1
and then it went inside the functionA, here it logged Starting Function A
, and then we hit the await statement where we have to wait for the Promise to resolve, so both of our functions, functionA
and myFunction
runs separately. And our code pointer comes to log 3
. And as soon as promise resolves, it logs Ending Function A
and then 2
Now it might occur to you why we cannot use async/await
inside forEach.
Let's see the forEach function first:
const forEach = (callback)=>{
...
...
callback()
...
...
}
Now when we do:
myArray.forEach(async(item) => {
await doSomethingWith(item)
})
As soon as our code hit await statement, our doSomethingWith
function runs asynchoronously and our code pointer comes back execute the next line in forEach
and hence, it completes the whole forEach and our await statements are still yet to be resolved.
Let's see an example here:
const promiseFunction = async (item) => {
await new Promise((resolve) => {
setTimeout(resolve, 600);
});
};
const myArray = [1, 2, 3, 4, 5, 6, 7, 8];
const myFunction = () => {
console.log("Starting my Function");
myArray.forEach(async (item) => {
console.log("Starting forEach with item value", item);
await promiseFunction(item);
console.log("Ending forEach with item value", item);
});
console.log("Ending my Function");
};
myFunction();
And here is our logs:
Starting my Function
Starting forEach with item value 1
Starting forEach with item value 2
Starting forEach with item value 3
Starting forEach with item value 4
Starting forEach with item value 5
Starting forEach with item value 6
Starting forEach with item value 7
Starting forEach with item value 8
Ending my Function
Ending forEach with item value 1
Ending forEach with item value 2
Ending forEach with item value 3
Ending forEach with item value 4
Ending forEach with item value 5
Ending forEach with item value 6
Ending forEach with item value 7
Ending forEach with item value 8
You see it starts the next callback without ending the previous, that is because each callback is waiting for its await to be resolved. Hence we get to the end of the myFunction
but could not complete callback functions.
The workaround:
We can use await Promise.all with map function to have await for each of the callbacks. Here is how:
const promiseFunction = async (item) => {
console.log("Starting promiseFunction with item value", item);
await new Promise((resolve) => {
setTimeout(resolve, 600);
});
console.log("Ending promiseFunction with item value", item);
};
const myArray = [1, 2, 3, 4, 5, 6, 7, 8];
const myFunction = async () => {
console.log("Starting my Function");
await Promise.all(
myArray.map((item) => {
return promiseFunction(item);
})
);
console.log("Ending my Function");
};
myFunction();
We are simply making the array of Promises by:
myArray.map((item) => {
return promiseFunction(item);
})
promiseFunction
returns a function so by map
funtion, we have array of Promises. And we finally resolve all the promises by: await Promise.all
.
And here is our logs:
Starting my Function
Starting promiseFunction with item value 1
Starting promiseFunction with item value 2
Starting promiseFunction with item value 3
Starting promiseFunction with item value 4
Starting promiseFunction with item value 5
Starting promiseFunction with item value 6
Starting promiseFunction with item value 7
Starting promiseFunction with item value 8
Ending promiseFunction with item value 1
Ending promiseFunction with item value 2
Ending promiseFunction with item value 3
Ending promiseFunction with item value 4
Ending promiseFunction with item value 5
Ending promiseFunction with item value 6
Ending promiseFunction with item value 7
Ending promiseFunction with item value 8
Ending my Function
Now we can see, although each promiseFunction
runs asynchronously, but we ensure the our myFunction
ends only when our all the Promises get resolved.
Hope you learned something today.
Happy Learning ❤️
Top comments (4)
Awesome article! If you were curious, you can add syntax highlighting to your code snippets by adding the language after the opening backticks like so:
which produces
You can also create your own helper function that does a similar job compared to
Array.prototype.forEach
. Here using a function declaration to not pollute theArray.prototype
.Not as sexy as polluting the
Array.prototype
but it does the trick as expected.Thank you! 🙏
Been there and done that!