Contents
When it comes to running asynchronous operations for each element in an array, the instinct is often to turn to .forEach()
method; it’s the go-to tool for looping over arrays. It seems like the perfect solution, right? However, there’s a pitfall that might leave you scratching your head when things don’t work as expected.
Why async/await Inside forEach May Catch You Off Guard
Let us consider this scenario: you have an array of user IDs, and for each user ID, you need to fetch the user and push their username into an array. Eventually, you want to log all the usernames. But, much to your surprise, the array remains empty. What gives?
|
|
To understand what is happening, let’s dive into the inner workings of this code. And what better way to understand it than through an animated depiction of the JavaScript runtime as it meticulously executes each line of your program?
Animation Courtesy: Maxim Orlov
Deep Dive into the Code
As the code starts to run, you’ll notice something intriguing. The fetchUserById()
requests are initiated concurrently within the forEach()
method. These requests are then dispatched to background tasks, waiting until they’re completed; whether it’s through WebAPIs in a browser`` or the
C++ thread pool` in Nodejs.
With the requests underway, the program moves ahead and logs the usernames array. However, don’t be surprised to find an empty array staring back at you. Why, you would ask?
Here is the twist: functional JavaScript methods like forEach()
are oblivious to promises. The callback functions they execute fire synchronously, never pausing for promises
to complete that might be lurking in between iterations.
And then, after a bit of time has passed, the requests wrap up, and the usernames array finally gets populated. This happens after the program has moved on and exited, leaving the usernames lingering in obscurity.
Ensure Code Execution Once All Asynchronous Tasks have Concluded
for...of
loop is one of the best way for such kind of scenarios.
|
|
Alternatively, Promise.all()
and Array.map()
methods can also be used to achieve the same.
|
|
Why forEach can’t be “fixed”
The ECMAScript spec says
forEach()
invokes its callback synchronously and ignores the return value. Even if the callback returns a Promise, forEach simply drops it on the floor.
Cheatsheet for Async/Await
Goal | Safe pattern | |
---|---|---|
Wait for every async job to finish | : | ->await Promise.all(array.map(fn)) |
One job at a time, in order | : | ->for (const x of array) await fn(x) |
Fire-and-forget without waiting | : | -> Still avoid forEach ; instead push into a queue or use a worker. |
Conclusion
Remember, in the realm of asynchronous JavaScript, understanding the intricacies of operations like forEach()
and the elegance of async/await
can make all the difference between code that works flawlessly and code that leaves you scratching your head. So, before you dive into your next array-based adventure, take a moment to consider the asynchronous journey that lies ahead. Your future self will thank you!
✨ Thank you for reading and I hope you find it helpful. I sincerely request for your feedback in the comment’s section.