Explain Codes LogoExplain Codes Logo

Using async/await with a forEach loop

javascript
async-await
promise-all
for-await-of
Anton ShumikhinbyAnton Shumikhin·Aug 19, 2024
TLDR
// Use Promise.all with map for Avengers Assemble!-style teamwork const processData = async (item) => { // Your async operation here, e.g., reading Thor's diary }; const items = [/* list of items to process, like your fellow Avengers */]; const results = await Promise.all(items.map(processData));

By using Promise.all combined with map, you effectively form an Avengers-style team out of your asynchronous functions (processData). Working on each item of your array (items) concurrently, this pattern ensures code cleanliness and completion of all operations before proceeding.

Circumventing pitfalls of forEach

For humdrum functions applied to each element in an array, forEach generally performs admirably. However, the reliable mule trip ups when it comes to async functions. The trouble lies in forEach not waiting for promises to resolve, giving rise to situations where you may end up using results that aren't ready.

go forth with for...of

In cases where maintaining the order of operations matters, a for...of loop runs the show. It processes each task sequentially – just like how we learned to tie our shoelaces.

// Read files sequentially with for...of const readFilesSequentially = async (files) => { // Tie shoelaces, one step at a time for (const file of files) { const content = await fs.readFile(file, 'utf8'); console.log(content); // Life's mysteries revealed after the reading } };

Concurrency with Promise.all and map

When the sequence of execution is unimportant, but parallelism can drive efficiency, a dashing duo of Promise.all and map should be your choice. This approach can kick-start all async operations simultaneously, wrapping up only when all operations are done.

Drawing the line with reduce

To exert finer control over the sequence of operations and handle intermediate outcomes, wielding reduce is the way to go. Start the ball rolling with Promise.resolve(), ensuring each operation follows its predecessor.

// In an orderly manner, please! const readFilesInOrder = files.reduce(async (previousPromise, file) => { await previousPromise; const content = await fs.readFile(file, 'utf8'); console.log(content); }, Promise.resolve());

Balancing parallel and ordered operations

When your tasks aren't quite independent, choosing to execute them in parallel isn’t about brevity; it’s about maximizing resource use. For IO-bound tasks like data fetching or file reading, using Promise.all with map can significantly enhance efficiency.

But if you can't juggle multiple balls at once, because they are CPU-bound tasks, or have sequentially dependent side-effects, all-you-can-eat buffet style isn't a good idea. Instead, take your time with for...of or reduce.

Mastering error control

With async operations, you also need to cover your bases when it comes to error handling. Promise.all breaks if it encounters even one rejection, so it's your job to couch individual operations in protective error handling.

ES2018's gem: for await...of

The devs who gave us ES2018 must’ve really detested complicated async code because they armed us with for await...of. This syntax makes asynchronous iteration a cakewalk, even when juggled with Streams or any other object implementing the asynchronous iteration protocol.