Using Promises More Effectively in Node.js

• about 900 words

Using Bluebird, Promisifying Modules, Mocha 1.18

At ruhmes­meile, we recently implemented a complex web application in Node.js and made heavy use of promises — in server-side code as well as in tests. Here’s what worked and what I’d do differently next time.

This arti­cle was also pub­lished on Medium.

Using Blue­bird

In gen­eral, I think promises are an amaz­ing con­cept. Not just because I had that one course in col­lege about Haskell and mon­ads, but because they sim­plify how you can rea­son about your code. Also, they help you han­dling those syn­chro­nous errors with great stack traces.

We set­tled on using the then recently released Q 1.0. We had been using ear­lier ver­sion of it in other pro­jects already and were quite famil­iar with the API.

Now, I’d choose Blue­bird. In addi­tion to most of Q’s good­ness, it offers some new fea­tures like catch­ing spe­cific error classes and gen­er­a­tor sup­port, but what really seals the deal are the even bet­ter stack traces and the amaz­ing per­for­mance (more infor­ma­tion).

Addi­tion­ally, Blue­bird seems to pre­fer some good prac­tices, e.g. treat­ing a promise like a class instance and using new Promise() (API) instead of using Promise.defer().

Promisi­fy­ing Mod­ules

Most of our asyn­chro­nous meth­ods are pretty straight-for­ward: They are promise chains. Some are short, some are rather com­plex, call­ing helper mod­ules and doing sev­eral things in par­al­lel. E.g., using Q we wrote code sim­i­lar to:

Q.fapply(function getTeamById () {…})
 .then(function getFileById (team) {…})
 .then(function deleteVideoOnVimeo (team) {…})
 .then(function removeFileFromTeam (team) {
   return Q.ninvoke(team, 'save');
  })
 .spread(function success (team, rowsAffected) {…})
 .fail(next);

(In this exam­ple, all func­tions in the promise chain were writ­ten inline– even though they have names for bet­ter stack traces. You could also declare all func­tions and then write the chain with just ref­er­ences. Even bet­ter, write abstract func­tions that use cur­ry­ing and can be used in sev­eral chains.)

But some third-party mod­ules did­n’t sup­port promises (or at least, don’t sup­port them well — like Mon­goose). To keep our code con­sis­tent and always use promises, we often used Q’s ninvoke() (Blue­bird offers .promisify() and there is also a mod­ule called nin­voke that clones Q’s API for Blue­bird). Ide­ally, we would replace the save method of the model with one that auto­mat­i­cally returns a promise.

Another mod­ule we should have wrapped to return promises is Super­A­gent, a really help­ful mod­ule to write short and pre­cise HTTP tests. In fact, we already made a sub­class of it to sup­port set­ting cus­tom head­ers (for autho­riza­tion using JWS), so over­writ­ing the .end() method to return a promise should be easy and pos­si­ble remove some very ugly ninvoke() calls.

If you think this all the way through, we should prob­a­bly have also writ­ten a thin wrap­per around res­tify’s route han­dler to make the con­trollers a bit leaner. Cur­rently, those are func­tions with a sig­na­ture of (request, response, next) -> void, but most would work equally well as (request) -> Promise, where the promise’s state deter­mines whether the data should be send as response (with sta­tus code 200) or it should be prop­a­gated as an error. (This would­n’t work for stream­ing responses, of course.)

Promises in Mocha Tests

Our API tests are writ­ten using Mocha, Chai and the afore­men­tioned Super­A­gent.

Most of our API tests look like this (in Cof­fee­Script):

describe "Post API", ->
  before (done) -> …

  describe "GET /posts", ->
    it "should retrieve an array of posts", (done) ->
      request(app)
      .get(postsPath)
      .expect(200)
      .end (err, res) ->
        return done(err) if err
        expect(res.body).to.be.an('array')
        expect(res.body.length).to.be.gt 0
        done()

That’s an easy case: When you give Mocha’s it() method a func­tion that has one argu­ment, it’s treated as a call­back that deter­mines when the test is done (with suc­cess or fail­ure). But what about a test where we first cre­ate sev­eral posts, then log in an mod­er­a­tor and check that all are vis­i­ble? Lot’s of Q.invoke(makePost(), 'end') await us if we want to avoid call­back-hell.

But, fear not: Since we started the pro­ject, Mocha 1.18 was released and with it a very nice fea­ture: Promise sup­port. That means, instead of call­ing a done() call­back, you can instead just return a promise. The promise’s final state will then deter­mine if the test was suc­cess­ful or not.