(Un)parallel processes in Node.js

(Un)parallel processes in Node.js

A roller coaster ride through promises and API/DB calls.

A year ago, I flunked an interview saying parallel executions were not possible in node.js when asked to execute a set of REST API calls parallelly. Of course, the right answer was to use Promise.all();

Although I acknowledged that I was wrong, something irked me. I couldn't get these questions out of my head: Isn't Javascript supposed to be single-threaded? how can parallel execution be possible in a single-threaded language??

After spending a while on multiple articles and reading everything right from definitions, I reconfirmed that node.js indeed is single-threaded. But this confirmation has only increased my confusion. Because being single-threaded is literally the opposite of parallel execution. So, I did what any staunch believer of the scientific method would do. I decided to test it out. I resorted to my code editor and wrote this piece of code:

import axios from 'axios';
let arr = [];
async function getData(){
    console.time('execution time')
    for(let i = 1; i< 10; i++){
        arr.push(await axios.get(`https://pokeapi.co/api/v2/pokemon/${i}`));
    }
    console.timeEnd('execution time')
}
getData();

Basically, I'm making 10 API calls to the pokemon API and awaiting each one of them. The following was the execution time:

image.png

And then, I've re-written the same piece of code using promise.All();

import axios from 'axios';
let arr = [];
async function getData(){
        console.time('execution time')
        for(let i = 1; i< 10; i++){
            arr.push(axios.get(`https://pokeapi.co/api/v2/pokemon/${i}`))
        }
        Promise.all(arr).then(()=>console.timeEnd('execution time'))
}
getData();

Now, if my understanding about Node.js being single-threaded is right, the performance difference shouldn't be much as only one process can be executed at a time. So, the moment of truth has come. I ran the code and saw this:

image.png

The execution time has decreased dramatically. After a roller coaster of thought processes and google searches, I finally understood why.

Reason

The reason for this performance difference lies in the way any API/DB fetching libraries are implemented on a fundamental level.

Whenever we're trying to make an API call, the part where HTTP requests are made are handled by some low-level c/cpp code and JavaScript only awaits the result of these operations. So, when we use promise.all(), it lets the low-level code perform HTTP operations without awaiting each one of their responses before going to another. We are initiating all the API calls in the order of calling them. But, their callbacks are added to the event loop only after the low-level code completes the execution.

In the case of Promise. all(), since we are not awaiting each API call, JavaScript asks low-level code to execute multiple calls without caring for the previous ones. And whenever low-level code is ready with their results, it adds them back to the event loop. So, this is obviously faster than awaiting each call and creates an illusion of Parallel execution!

Verification:

Just to be sure before publishing this article, I decided to test out if my conclusion is right. So, I wrote a function to be awaited which is completely in my control.

Here, each call in the for loop will take 1sec exactly.

let arr = [];
function timeout(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

async function verification(){
    console.time('execution time')
    for(let i = 1; i< 10; i++){
        arr.push(await timeout(1000));
    }
    console.timeEnd('execution time')
}

verification();

The execution time is: image.png

Now, I tried the same with Promise.all();

let arr = [];
function timeout(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

async function verification(){
    console.time('execution time')
    for(let i = 1; i< 10; i++){
        arr.push(timeout(1000));
    }
       Promise.all(arr).then(()=>console.timeEnd('execution time'))
}

Aaaand Bravo! The execution time is the same!

image.png

Parallel execution is indeed impossible in JavaScript!