Node.js: Using Promise to Avoid Callback Hell

This article shows how you can use Promise instead of Callback functions while writing the synchronous programming code in Node.js.

Node.js is asynchronous/non-blocking in nature. If we want to execute certain code only after the execution of another code then we can use Callback functions. However, there can be a need of using nested callback functions which results in a situation called Callback Hell.

Previously, I have written two articles on Node.js asynchronous nature and using callback functions:

1. Node.js: Asynchronous & Synchronous Code Programming
2. Node.js: Using Callback Functions for Synchronous Programming

The Promise object represents whether an asynchronous operation has been completed (resolved) or failed (rejected) along with that operation value.

First of all, we will see a simple example without using callback or promise.

  • Open terminal
  • Go to your Node.js application directory
  • In my case it is:

cd ~/Documents/nodejs

mukesh:nodejs chapagain$ pwd
/Users/mukeshchapagain/Documents/nodejs
  • Create a file app.js and add the following code to it

app.js


function getUserData(userId) {
    setTimeout(function() { 
        console.log('USER ID: ' + userId);
        let userName = 'Mukesh Chapagain';
        return userName;
    }, 1000); // 1000 miliseconds = 1 second
}

console.log('Start');
let userId = 2;
let userData = getUserData(userId);
console.log(userData);
console.log('End');
  • Run the application

node app.js

Output:


mukesh:nodejs chapagain$ node app.js
Start
undefined
End
USER ID: 2

You can see the undefined value. It’s because there is a delay of 1 second in the getUserData() function. So, the code console.log(userData) executes before the getUserData() function returns the value.

Using Callback function

We will now use the callback function in the example code above so that we can get the value from the getUserData() function and print the return value.

app.js


function getUserData(userId, callback) {
    setTimeout(function() { 
        console.log('USER ID: ' + userId);
        let userName = 'Mukesh Chapagain';
        return callback(userName);
    }, 1000); // 1000 miliseconds = 1 second
}

console.log('Start');
let userId = 2;
getUserData(userId, function(result) {
    console.log('Printing the return value');
    console.log(result);
    console.log('End');
});
  • Run the application

node app.js

Output:


mukesh:nodejs chapagain$ node app.js
Start
USER ID: 2
Printing the return value
Mukesh Chapagain
End

Using Promise

We will now use Promise in the same example code above so that we can get the value from the getUserData() function and print the return value.

app.js


function getUserData(userId) {
    return new Promise(function(resolve, reject) {
        setTimeout(function() { 
            console.log('USER ID: ' + userId);
            let userName = 'Mukesh Chapagain';
            resolve(userName); // resolve or reject is used instead of return
        }, 1000); // 1000 miliseconds = 1 second
    }); 
}

console.log('Start');
let userId = 2;
let userData = getUserData(userId);
userData.then(function(result) {
    console.log('Printing the return value');
    console.log(result);
    console.log('End');
}).catch(function(error) { 
    console.log(error); 
});
  • Run the application

node app.js

Output:


mukesh:nodejs chapagain$ node app.js
Start
USER ID: 2
Printing the return value
Mukesh Chapagain
End

Asynchronous File Read

Now, let’s see an example of multiple file read. We will try to read multiple files in Node.js and print the content of those files.

  • Open terminal
  • Go to your Node.js application directory
  • In my case it is:

cd ~/Documents/nodejs

mukesh:nodejs chapagain$ pwd
/Users/mukeshchapagain/Documents/nodejs
  • Create three text files with some content in them.

mukesh:nodejs chapagain$ echo "Data from File 1" >> myfile_1.txt
mukesh:nodejs chapagain$ echo "Data from File 2" >> myfile_2.txt
mukesh:nodejs chapagain$ echo "Data from File 3" >> myfile_3.txt
  • Create a file app.js and add the following code in it

app.js


const fs = require('fs');

function read(fileName) {
    fs.readFile(fileName, 'utf-8', function(err, data) {
        if (err) { throw err; }     
        return data;
    });
}

console.log('Start');
console.log(read('myfile_1.txt'));
console.log(read('myfile_2.txt'));
console.log(read('myfile_3.txt'));
console.log('End');
  • Run the application

node app.js

Output:


mukesh:nodejs chapagain$ node app.js
Start
undefined
undefined
undefined
End

We get undefined data from all of the files we are trying to read because of the asynchronous nature of Node.js code.

We will solve this issue with two ways:

  • Using Callback functions
  • Using Promise

Using Callback Function

We will use Callback functions to read multiple files and print their respective content/data.

app.js


const fs = require('fs');

function read(fileName, callback) {
    fs.readFile(fileName, 'utf-8', function(err, data) {
        if (err) { throw err; }     
        return callback(data);
    });
}

read('myfile_1.txt', function(data) {
    console.log('Start \n');
    console.log(data);
    read('myfile_2.txt', function(data) {
        console.log(data);
        read('myfile_3.txt', function(data) {
            console.log(data);
            console.log('End');
        })
    });
});
  • Run the application

node app.js

Output:


mukesh:nodejs chapagain$ node app.js
Start

Data from File 1

Data from File 2

Data from File 3

End

Using Promise

Using Promise is a better way to handle this issue as compared to using of Callback functions. So, here we will use Promise to read multiple files and print their respective content/data.

app.js


const fs = require('fs');

function read(fileName) {
    return new Promise(function(resolve, reject) {
        fs.readFile(fileName, 'utf-8', function(err, data) {
            if (err) { 
                reject(err);
            } else {
                resolve(data);
            }           
        });
    }); 
}

console.log('Start \n');

let dataFirst = read('myfile_1.txt');
dataFirst.then(function(resultFirst) {
    console.log(resultFirst);

    let dataSecond = read('myfile_2.txt');
    dataSecond.then(function(resultSecond) {
        console.log(resultSecond);

        let dataThird = read('myfile_3.txt');
        dataThird.then(function(resultThird) {
            console.log(resultThird);
            console.log('End');
        }).catch(function(error) { console.log(error) });

    }).catch(function(error) { console.log(error) });

}).catch(function(error) { console.log(error) });
  • Run the application

node app.js

Output:


mukesh:nodejs chapagain$ node app.js
Start

Data from File 1

Data from File 2

Data from File 3

End

Using All Promise at once

Instead of running the file read operation each time individually and using .then() every time, we can use Promise.all(iterable) method to execute all the nested promises at once and get the final result as an array of all individual promise result.

– An array of functions can be passed as an argument to Promise.all method.
– The Promise.all(iterable) method returns a single Promise that resolves when all of the promises in the iterable argument have resolved.
– It rejects with the reason of the first promise that rejects.

Here’s the basic syntax of Promise.all(iterable) method:


Promise.all([promise1, promise2, promise3]).then(function(values) {
  console.log(values);
});

We can use it in our file read operation like below:

app.js


const fs = require('fs');

function read(fileName) {
    return new Promise(function(resolve, reject) {
        fs.readFile(fileName, 'utf-8', function(err, data) {
            if (err) { 
                reject(err);
            } else {
                resolve(data);
            }           
        });
    }); 
}

console.log('Start \n');

Promise.all([
    read('myfile_1.txt'), 
    read('myfile_2.txt'), 
    read('myfile_3.txt')
]).then(function(values) {
  console.log(values);
  console.log('\nEnd');
});
  • Run the application

node app.js

Output:


mukesh:nodejs chapagain$ node app.js
Start

[ 'Data from File 1\n',
  'Data from File 1\n',
  'Data from File 1\n' ]

End

Hope this helps. Thanks.