Explain Codes LogoExplain Codes Logo

Is Node.js native Promise.all processing in parallel or sequentially?

javascript
async-await
promise-all
concurrent-processing
Nikita BarsukovbyNikita Barsukov·Feb 25, 2025
TLDR

Indeed, Promise.all in Node.js launches tasks concurrently—not sequentially. It activates all promises concurrently, wrapping up when all of them are fulfilled or a single one is rejected. That's your lightning answer.

Now, let's imagine a database-query scenario:

let promiseArray = [User.findById(1), User.findById(2), User.findById(3)]; Promise.all(promiseArray).then(users => console.log(`Users downloaded faster than you can say 'Sweater Weather'!`));

In this example, each findById kicks off without patiently waiting for its pals, hence performing the database searches in unison. The console.log, however, takes a nap until all users are located.

For Loop — traditional but still hip

Need to execute promises sequentially? Don't fret! Either deploy the Array.reduce() method or bring async/await into the equation contained within a loop. Here's reduce() in its full glory:

let promiseFactory = [fn1, fn2, fn3]; // These dudes return a Promise promiseFactory.reduce((prevPromise, nextFn) => prevPromise.then(nextFn), Promise.resolve()).then(result => console.log(`Executed in sequence. Feel like James Bond yet?`));

Got that? Perfect! Each promise cordially waits its turn and only gets initiated after the one before has wrapped up. Now, let's leap into the exciting world of async/await:

async function keepInLine(tasks) { for (const task of tasks) { await task(); // Politely making sure each task finishes its job before the next one comes into the picture. } }

The use of await within this loop guarantees a structured, orderly execution of tasks in a wonderfully asynchronous fashion.

Know thy Promise

Promises — born ready

Respect a new Promise! It doesn't dilly-dally. As soon as it steps into the world, it gets its executor function rolling. So, remember, by the moment you pass your promises to Promise.all, the tasks are already in full swing.

Promise.allSettled — the patient friend

Ever had one of those situations where you need to nail all results but don't want a single botched attempt to abort the entire mission? Say hello Promise.allSettled. This smooth opera sits tight, waiting for all promises to settle down (either fulfilled or rejected).

Concurrent vs. parallel processing

Here's the tea: despite its massive talent, Node.js can't boast of actual parallelism due to being single-threaded. However, it makes a brilliant show of handling "concurrent" tasks seamlessly, all thanks to the dynamic event loop that cleverly utilizes non-blocking I/O operations.

Diving deeper

Sequential processing with shared resources

In those instances when you need to share resources across your operations, a good old for loop synergizing with async/await could be your best companion:

let sharedResources = {}; async function danceInSequence(tasks, resources) { for (const task of tasks) { Object.assign(resources, await task(resources)); // Sharing is caring! } }

Using this code, each task gets a share of the resources and is even allowed to alter it before the next task steps in.

The art of promise chaining

Are you chaining with .then? Cool. Now remember to return a new Promise:

promise.then(result => { return new Promise((resolve, reject) => { // Some magical async operation happens here! }); });

Forgetting to return a promise could cause a commotion, disturbing the promise chain and resulting in a rather unruly error or unexpected behavior.

Extra finesse

Using recursion for sequential execution

Got a dynamic list of promises? Recursion can be a compact solution to make them march in sequence:

async function marchOneByOne(index, tasks) { if (index >= tasks.length) { return Promise.resolve(); // Hey, where did everyone go? } return tasks[index]().then(() => marchOneByOne(index + 1, tasks)); // Cool. Now, let's get the next one going. } // Let's begin the march! marchOneByOne(0, promiseFactories) .then(() => console.log("Finished marching. Time for ice cream!"));

Simulating async operations

setTimeout can act as a wonderful asynchronous operation stand-in for simulation purposes:

function pretendPromise(result, delay) { return new Promise(resolve => setTimeout(() => resolve(result), delay)) .then(() => console.log("This promise got fulfilled faster than a catfish on a hot tin roof!")); }

Now, you can test your promise logic without reliance on actual I/O tasks. How cool is that?

A stitch in time: error handling

It's crucial to incorporate error handling for your noble promise chains to prevent them from catching unhandled rejections:

Promise.all(promises) .then(results => console.log(results)) .catch(error => console.error("Houston, we have a problem!", error));

This intelligent measure can ensure your concurrent processing remains robust, even when the going gets tough.