Are nested promises normal in Node.js?

Viewed 72341

The problem I have been struggling with for two weeks now while learning Node.js is how to do synchronous programming using node. I found that no matter how I try to do things sequentially I always end up with nested promises. I have found that there are modules such as Q to help with promise chaining as far as maintainability.

What I don't understand while doing research is Promise.all(), Promise.resolve() and Promise.reject(). Promise.reject is pretty much self explanatory by the name but when writing an application I am confused on how to include any of these in functions or objects without breaking the behavior of the application.

There is definitely a learning curve to Node.js when coming from a programming language such as Java or C#. The question that still resides is if promise chaining is normal (best practice) in Node.js.

Example:

driver.get('https://website.example/login').then(function () {
    loginPage.login('company.admin', 'password').then(function () {
        var employeePage = new EmployeePage(driver.getDriver());

        employeePage.clickAddEmployee().then(function() {
            setTimeout(function() {
                var addEmployeeForm = new AddEmployeeForm(driver.getDriver());

                addEmployeeForm.insertUserName(employee.username).then(function() {
                    addEmployeeForm.insertFirstName(employee.firstName).then(function() {
                        addEmployeeForm.insertLastName(employee.lastName).then(function() {
                            addEmployeeForm.clickCreateEmployee().then(function() {
                                employeePage.searchEmployee(employee);
                            });
                        });
                    });
                });
            }, 750);
        });
    });
});
8 Answers

Since this post is a top result for "nested promises" on Google, and having struggled with promises in my early days of learning node.js from a C# background, I thought I'd post something that would help others making a similar transition/evolution.

The voted-up answer by Tate is totally correct in that it does force a sequence, but the issue for most .NET or Java developers is that we're just not used to so many things being async operations in a synchronous language. You have to be super-aware of what's async, because outer blocks continue & complete before any async action would.

To illustrate, here's some code (complete with nesting & two bugs!) I struggled with while learning promises with 'pg-promise':

            exports.create = async function createMeet(thingJson, res, next) {
    let conn;
    if (helpers.isDate(helpers.isStringDate(thingJson.ThingDate))){
        db.connect()
            .then(obj => {
                conn = obj;
                conn.proc('spCreateThing',[
                    thingJson.ThingName,
                    thingJson.ThingDescription,
                    thingJson.ThingDate])
                    .then(data => {
                        res.status(201).json(data);
                        res.send();
                    })
                    .catch(error =>{
                        console.error("Error creating a Thing via spCreateThing(" + thingJson + "): " + error);
                        next(createError(500, "Failed to create a Thing!"));
                    })
                    .finally(()  => {
                        conn.done(); //appropriate time to close the connection
                    });
                })
            .catch(error =>{
                console.error("Error establishing postgres database connection: " + error);
                next(createError(500, "Error establishing postgres database connection: " + error));
            })
            .finally(()  => { //this finally block will execute before the async actions fired in first .then() complete/start
                    conn.done(); //so this would close the connection before conn.proc() has completed/started
            });
        res.send(); //this will execute immediately following .connect() BEFORE any of the chained promise results,
        // thus sending a response before we've even figured out if the connection was successful and started the proc 
    } else {
        console.error("Attempt to create a Thing without valid date: " + thingJson.ThingDate);
        next(createError(400, "Must specify a valid date: " + thingJson.ThingDate));
    }

On top of that, the code that calls this function (i.e. a route handler) will complete before the db connection process even starts.

So, the net of it is that outer functions define the promise structure and initiate the async calls, but then immediately complete their block since JS is first a synchronous language; so be aware and assume all async calls don't even start until after the block that called it is complete.

I know this is obvious to career JS developers (and is to me now), but I hope this really helps others new to these concepts.

Yeah like @TateThurston said, we chain them. It's even more aesthetically pleasing when you use es6 arrow functions

Here's an example:

driver
    .get( 'https://website.example/login' )
    .then( () => loginPage.login( 'company.admin', 'password' ) )
    .then( () => new EmployeePage( driver.getDriver() ).clickAddEmployee() )
    .then( () => {
        setTimeout( () => {
            new AddEmployeeForm( driver.getDriver() )
                .insertUserName( employee.username )
                .then( () => addEmployeeForm.insertFirstName( employee.firstName ) )
                .then( () => addEmployeeForm.insertLastName( employee.lastName ) )
                .then( () => addEmployeeForm.clickCreateEmployee() )
                .then( () => employeePage.searchEmployee( employee ) );
        }, 750 )
    } );
Related