85

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);
        });
    });
});
Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
Grim
  • 2,398
  • 4
  • 35
  • 54

8 Answers8

120

No, one of the great advantages of Promises is that you you can keep your async code linear rather than nested (callback hell from continuation passing style).

Promises give you return statements and error throwing, which you lose with continuation passing style.

You need to return the promise from your async functions so you can chain on the returned value.

Here's an example:

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

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

Promise.all takes an array of promises and resolves once all promises resolve, if any are rejected, the array is rejected. This allows you to execute async code concurrently rather than serially, and still wait for the result of all concurrent functions. If you're comfortable with a threaded model, think spawning threads and then joining.

Example:

addEmployeeForm.insertUserName(employee.username)
    .then(function() {
        // these two functions will be invoked immediately and resolve concurrently
        return Promise.all([
            addEmployeeForm.insertFirstName(employee.firstName),
            addEmployeeForm.insertLastName(employee.lastName)
        ])
    })
    // this will be invoked after both insertFirstName and insertLastName have succeeded
    .then(function() {
        return addEmployeeForm.clickCreateEmployee()
    })
    .then(function() {
        return employeePage.searchEmployee(employee)
    })
    // if an error arises anywhere in the chain this function will be invoked
    .catch(function(err){
        console.log(err)
    });

Promise.resolve() and Promise.reject() are methods used when creating a Promise. They're used to wrap an async function using callbacks so that you can work with Promises instead of callbacks.

Resolve will resolve/fulfill the promise (this means a chained then method will be called with the resulting value). Reject will reject the promise (this means any chained then method(s) will not be called, but the first chained catch method will be called with the error that arose).

I left your setTimeout in place to preserve your programs behavior, but it's likely unnecessary.

Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
Tate Thurston
  • 4,236
  • 1
  • 26
  • 22
  • Thank You, this helped me alot. – Grim Mar 07 '16 at 13:22
  • @TateThurston Forgive me for being too noob. I have a question, does chained promises (like in your first example) execute sequentially? So let's say the first promise took around 1sec to complete the async request, the 2nd promise chained by `.then` won't get executed until the first one has finished, is that correct? And is it the same for the 3rd promise and so on? – JohnnyQ Jan 21 '17 at 14:37
  • 1
    Am I correct in understanding that if you have multiple async calls which rely on the result of another async call you will still have nested promises? There's really no way to avoid it. – Levi Fuller Mar 09 '17 at 17:51
  • @LeviFuller you can still keep things flat. If you have a specific case in mind, I'm happy to answer it. – Tate Thurston Mar 10 '17 at 20:39
  • @TateThurston That would be awesome. For example, how could I flatten something like this where very call relies on the result of the previous call? `getOrganization().then(orgId => getFirstEnvironment(orgId).then(environmentId => getAppList.then(appList => console.log(appList)))).catch(err => console.log(err));` – Levi Fuller Mar 10 '17 at 22:07
  • And when I say flatten, I don't mean a series of separately defined callback functions, I mean like truly inline like C# `var orgId = await getOrganization(); var envId = await getFirstEnvironment(orgId);` etc. – Levi Fuller Mar 10 '17 at 22:14
  • @LeviFuller flattening the promises you provided follows an identical process as my answer to the question above. Here is the result: `getOrganization()` `.then(orgId => getFirstEnvironment(orgId))` `.then(environmentId => getAppList())` `.then(appList => console.log(appList))` `.catch(err => console.log(err));` – Tate Thurston Mar 13 '17 at 14:56
  • You have a .then inside a .then – Zack117 Nov 27 '18 at 18:54
10

Use async library and use async.series instead of nested chainings which looks really ugly and hard to debug/understand.

async.series([
    methodOne,
    methodTwo
], function (err, results) {
    // Here, results is the value from each function
    console.log(results);
});

The Promise.all(iterable) method returns a promise that resolves when all of the promises in the iterable argument have resolved, or rejects with the reason of the first passed promise that rejects.

var p1 = Promise.resolve(3);
var p2 = 1337;
var p3 = new Promise(function(resolve, reject) {
  setTimeout(resolve, 100, "foo");
}); 

Promise.all([p1, p2, p3]).then(function(values) { 
  console.log(values); // [3, 1337, "foo"] 
});

The Promise.resolve(value) method returns a Promise object that is resolved with the given value. If the value is a thenable (i.e. has a then method), the returned promise will "follow" that thenable, adopting its eventual state; otherwise the returned promise will be fulfilled with the value.

var p = Promise.resolve([1,2,3]);
p.then(function(v) {
  console.log(v[0]); // 1
});

https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

Thalaivar
  • 23,282
  • 5
  • 60
  • 71
  • You by far have the cleanest example using async.series and Promise.all. Does results for async.series behave exactly like promise.all values to where it is an array of values? – Grim Mar 04 '16 at 21:04
  • Also lets say I use async.series() in a function inside an object and I need to chain promises inside that function. Would I return async.series() or return nothing to the caller? – Grim Mar 04 '16 at 21:06
  • 7
    There is no reason here to use the `async` library to avoid nesting. The OP can chain their promise calls without nesting. – jfriend00 Mar 04 '16 at 21:54
  • @jfriend00 When would you use the async library? – Grim Mar 04 '16 at 22:56
  • 2
    @CharlesSexton - Once you learn how to use promises and grow accustomed to the enhanced error handling for async operations and general ease of management of async operations, you will never want to write async code without it, I've not found anything that the async library offers that can't be done just as easily or more easily with something like the Bluebird promise library (which is what I use). I don't use the async library because I've not found a reason to once I learned promises. – jfriend00 Mar 04 '16 at 23:01
  • This is silly, you're importing another library for no good reason rather than a stronger tool that is already included.\ – Benjamin Gruenbaum Mar 05 '16 at 09:40
  • @jfriend00: Tools are of your choice, async library works great and the async.series is one of the cleanest... again you have a different opinion, so its more of choice. – Thalaivar Mar 05 '16 at 13:04
  • @Thalaivar: I am using `async.series` but I am facing an issue. Only methodOne is executing rest is not executing. why? – Jamshaid Alam May 10 '20 at 14:00
3

I just answered a similar question where I explained a technique that uses generators to flatten Promise chains in a nice way. The technique gets its inspiration from co-routines.

Take this bit of code

Promise.prototype.bind = Promise.prototype.then;

const coro = g => {
  const next = x => {
    let {done, value} = g.next(x);
    return done ? value : value.bind(next);
  }
  return next();
};

Using it, you can transform your deeply-nested Promise chain into this

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

Using named generators, we can make that even more legible

// don't forget to assign your free variables
// var driver = ...
// var loginPage = ...
// var employeePage = new EmployeePage(driver.getDriver());
// var addEmployeeForm = new AddEmployeeForm(driver.getDriver());
// var employee = ...

function* createEmployee () {
  yield addEmployeeForm.insertUserName(employee.username);
  yield addEmployeeForm.insertFirstName(employee.firstName);
  yield addEmployeeForm.insertLastName(employee.lastName);
  yield addEmployeeForm.clickCreateEmployee();
  yield employeePage.searchEmployee(employee);
}

function* login () {
  yield driver.get('https://website.example/login')
  yield loginPage.login('company.admin', 'password');
  yield employeePage.clickAddEmployee();
  setTimeout(() => coro(createEmployee()), 750);
}

coro(login());

However, this only scratches the surface of what's possible using co-routines to control the flow of promises. Read the answer I linked above that demonstrates some of the other advantages and capabilities of this technique.

If you do intend to use co-routines for this purpose, I encourage you to check out the co library.

PS not sure why you're using setTimeout in this fashion. What is the point of waiting for 750 ms specifically?

Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
Mulan
  • 129,518
  • 31
  • 228
  • 259
  • The setTimeout is used to wait for the browser to render a new page and then perform those actions, Node.js is really fast when doing automation testing. I need to do some more research but this a really good answer and might be the best answer. – Grim Mar 04 '16 at 21:24
  • @CharlesSexton well there should be some way you can wrap a callback/promise around that. I'm not sure what testing library you're using, but waiting an arbitrary amount like that is silly. – Mulan Mar 04 '16 at 21:27
  • I was using mocha/Chai but I have been just writing scripts for practice with promises. What testing library do you recommend for automation testing? I thought promises was a better design approach than callbacks. I probably could wrap it in a callback but I would have to play with it to see. – Grim Mar 04 '16 at 22:55
  • mocha/chai doesn't have methods like `.insertUserName` or `.clickAddEmployee`. – Mulan Mar 05 '16 at 00:08
  • insertUserName and clickAddEmployee is from a page object. I'm not actually checking anything but that I can add a user and then search for that user in the list after being added – Grim Mar 05 '16 at 01:14
  • Don't roll your own coroutines if you don't understand promises exceptionally well yet. It's great that you got a good enough grasp to implement coroutines but your example is missing error handling and a bunch of other stuff. – Benjamin Gruenbaum Mar 05 '16 at 09:42
  • > If you do intend to use coroutines for this purpose, I encourage you to check out the [co](https://github.com/tj/co) library. – Mulan Mar 05 '16 at 09:44
3

I removed the unnecessary nesting. Ill use syntax from 'bluebird'(my preferred Promise library) http://bluebirdjs.com/docs/api-reference.html

var employeePage;

driver.get('https://website.example/login').then(function() {
    return loginPage.login('company.admin', 'password');
}).then(function() {
    employeePage = new EmployeePage(driver.getDriver());
    return employeePage.clickAddEmployee();
}).then(function () {
    var deferred = Promise.pending();
    setTimeout(deferred.resolve,750);
    return deferred.promise;
}).then(function() {
    var addEmployeeForm = new AddEmployeeForm(driver.getDriver());
    return Promise.all([addEmployeeForm.insertUserName(employee.username),
                        addEmployeeForm.insertFirstName(employee.firstName),
                        addEmployeeForm.insertLastName(employee.lastName)]);
}).then(function() {
    return addEmployeeForm.clickCreateEmployee();
}).then(function() {
    return employeePage.searchEmployee(employee);
}).catch(console.log);

I modified your code to include examples for all you questions.

  1. There is no need to use the async library when working with promises. Promises are a very powerful by themselves and I think its an anti-pattern to mix promises and libraries like async.

  2. Normally you should avoid using the var deferred = Promise.pending() style...unless

'when wrapping a callback API that doesn't follow the standard convention. Like setTimeout:'

https://github.com/petkaantonov/bluebird/wiki/Promise-anti-patterns

For the setTimeout example..create a 'deferred' promise...resolve the promise inside setTimeout and then return the promise outside setTimeout. This might seem a little unintuitive. Look at this example, I answered another question. Q.js promise with node. Missing error handler on `socket`. TypeError: Cannot call method 'then' of undefined

Normally, you can get away with using Promise.promisify(someFunction) to convert a callback type function into a Promise returning function.

  1. Promise.all Lets say your are making multiple calls to an service that return asynchronously. If they don't depend on each other, you can make the calls simultaneously.

Just pass the function calls as an array. Promise.all([promiseReturningCall1, promiseReturningCall2, promiseReturningCall3]);

  1. Finally add a catch block to the very end..to make sure you catch any error. This will catch any exception anywhere in the chain.
Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
  • Where do you get `Promise.pending()` from? That is not a standard Promise method that I'm aware of. Why not just use the standard promise constructor? Also, an arbitrary delay inserted in this process (which I know was not your idea, but came from the OP) smells like a hack that is probably not the proper way to code this. – jfriend00 Mar 04 '16 at 23:05
  • Promise.pending is a bluebird syntax. I agree the setTimeout usage looks funny. I left it in to explain manual Promise creation. – Arun Sivasankaran Mar 04 '16 at 23:26
  • Then two points. 1) You should say in your answer that you're relying on Bluebird (that is my preferred promise library too) since it extends beyond standard promises. 2) If you're using Bluebird, then you may as well use `Promise.delay()` instead of `Promise.pending()` and `setTimeout()`. – jfriend00 Mar 04 '16 at 23:28
  • I updated my answer to mention 'bluebird'. Promise.delay is infact the better way to add manual delays. – Arun Sivasankaran Mar 04 '16 at 23:35
  • Interesting that I don't see `Promise.pending()` in the Bluebird API doc page. Is that an older or newer API? – jfriend00 Mar 05 '16 at 00:13
  • There is no reason to use bluebird here. Everything you use here is included with the `$q` library and importing a 15kb promise library just for this is silly. If you're going to use bluebird with Angular you _have_ to call `setScheduler` first. – Benjamin Gruenbaum Mar 05 '16 at 09:41
2

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.

  • For one thing, you should use `async/await` syntax, which is much simpler. And for another, you are making not needed `connect` call with pg-promise. Your database code can be reduced to just one line - `await db.proc(...)` and that's it. – vitaly-t May 22 '21 at 14:02
1

Your next step is to go from nesting to chaining. You need to realize that each promise is an isolated promise that can be chained in a parent promise. In other words, you can flatten the promises to a chain. Each promise result can be passed to the next one.

Here is a great blog post about it: Flattening Promise Chains. It uses Angular but you can ignore that and look at how a deep nesting of promises turns into a chain.

Another good answer is right here on StackOverflow: Understanding javascript promises; stacks and chaining.

Community
  • 1
  • 1
Cymen
  • 14,079
  • 4
  • 52
  • 72
0

You can chain promises like this:

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

    return employeePage.clickAddEmployee().then(function() {
        setTimeout(function() {
            var addEmployeeForm = new AddEmployeeForm(driver.getDriver());
        return addEmployeeForm.insertUserName(employee.username).then(function() {
                retun addEmployeeForm.insertFirstName(employee.firstName)
         }).then(function() {
                return addEmployeeForm.insertLastName(employee.lastName)
         }).then(function() {
             return addEmployeeForm.clickCreateEmployee()
         }).then(function () {
             retrun employeePage.searchEmployee(employee);
        })}, 750);
        });
    });
});
Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
tubu13
  • 924
  • 2
  • 17
  • 34
  • 1
    I am confused by the multiple returns. You have .then(function() { return statement }); When it is used like that where does the returned value go or in this case the promise? In synchronous programming the return goes back to the caller. What is the caller? – Grim Mar 04 '16 at 20:59
0

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 )
    } );
Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
shramee
  • 5,030
  • 1
  • 22
  • 46