fbpx

Collect results from multiple promises in NodeJS

Promises can be complicated by themselves and may take time for a programmer who is otherwise comfortable with a procedural style of programming.

In certain cases, you maybe required to not just executed one promise, but execute multiple promises and collectively process the results from each of the promises. This article provides an example of the same.

let promises = [];
  
let numbers = [1,2,3,4,5];
numbers.forEach(number => {
let promise = new Promise(function(resolve, reject){
    resolve (number * number);
  });
  promises.push(promise);
})

Promise.all(promises).then(squares => console.log(JSON.stringify(squares)));

The above program performs a square of each of the numbers in an array, but each square operation is executed asynchronously. The return of the asynchronous operation is got using a promise.

Each promise is created within an array and added to a promises array.

Promise.all() function is called that invokes and awaits execution of all the promises. Once every promise is resolved (or rejected), the result of each promise is collected into an array, in our case this is the “squares” variable.

The output of the above program looks as shown below

[1,4,9,16,25]

You can notice that the output is in the same order as the input numbers. Rather it is in the same order as the “promises” array. The Promise.all() function ensures that ordering of responses is maintained.

Let us take another example.

let promises = [];
  
let numbers = [-1,0,1,2,3,4,5];
numbers.forEach(number => {
let promise = new Promise(function(resolve, reject){
    if(number < 0) reject('Only positive numbers accepted');
    else resolve (number * number);
  }).catch(err => console.log(err));
  promises.push(promise);
})

Promise.all(promises).then(squares => console.log(JSON.stringify(squares)));

The above program rejects the scenario for squaring a negative number. Let’s see what the output looks like in this case.

Only positive numbers accepted
[null,0,1,4,9,16,25]

The first log line comes from the “catch” condition in the promise itself. The second line shows output after Promise.all(). We can see that we have a “null” value for square of -1, as the promise rejected a negative number.

The ordering of responses in the array is still maintained even if any of the promise is rejected. The array will contain a “null” value for promises that either reject or ones that resolve with a “null” value.

learn-ai-and-ml

Why does NodeJS scale insanely?

If you are new to NodeJS, you might have heard that NodeJS is single threaded. And you might have also heard that it is insanely scalable, serving millions of users at realtime. How does a single threaded application scale so well?

Single threading is half the truth

Yes, NodeJS follows a Single Thread Event Loop Model, but it is not actually single threaded. It works on an event based execution architecture.

NodeJS has a main thread and additional worker threads. Tasks that do not have to be serviced synchronously can be passed onto the worker threads. When worker threats are ready to be executed, they report back to the event loop. The event loop picks up an event and passes it to the main program stack for being the next in line for execution.

This provides a single threaded, but sudo parallel execution environment.

Understanding NodeJS Execution

const request = require('request');
let f1 = function() {
  console.log('Hello at beginning');
  request('https://google.com', (err, res, body) => {
    console.log('Hello from function');
  });
  console.log('Hello at end');
}

f1();

If we executed the above code in a procedural manner, we would expect the following output.

Hello at beginning
Hello from function
Hello at end

However your NodeJS application will show the following output.

Hello at beginning
Hello at end
Hello from function

Why is this so? Why does the request() line execute after the last console.log() statement? This is so because invoking request() is an asynchronous task. The execution of this task gets allotted to a worker thread. While the worker thread waits to get the response from google.com, the main thread can continue with further execution. This results in the last console output being printed while the worker thread is waiting for a response on the request.

When the worker thread does receive a response, it puts an entry into the event loop. When the main thread is free and doing nothing else, it picks up an event from the event loop and executes the tasks that was allotted to the worker. The event loop tasks are only executed when the main thread is free and not performing any other task.

NodeJS Async Execution
Call to request() passed on to a worker thread

So why is NodeJS insanely scalable?

This unique event based model prevents NodeJS from being blocked by any specific event. Each event is treated and processed independent of each other. This is only true as long as you don’t write code that blocks the main event thread.

Since async function calls report back to an event loop for execution when they are ready to be executed, the main thread is always busy doing something and never waiting on any task. A properly designed NodeJS application, can thereby keep the main event loop free from long running tasks, by passing long running tasks to worker threads.

This concept is very different than spawning new threads for executing tasks in parallel. There is a physical limit to the number of threads a system can execute. When this limit is reached, if individual threads are waiting for a long running operation to complete, all threads would essentially wait, thereby making the complete application slow.

On the contrary, in NodeJS, the main event loop only gets those tasks to execute that are ready to be executed. Thereby millions of concurrent events can be created, without affecting the performance of the main thread, thereby allowing for significant scalability of applications that are well designed.

NodeJS is turning out to be one of the preferred backend systems for web applications and web services.