Chained promise-syntaxes

I started using javascript promises about half a year ago, and I still haven't settled on the best syntax for cases with chained promises. But I do believe I use a better syntax than what I've found in other blogs.

The problem with chained promises

You have several functions returning promises of values that you want to keep, but you have to run them after eachother.

I searched a little for best practices regarding this, but didn't really find any satisfactory answer. Nolan Lawson adresses the problem (and some promise anti-patterns) in We have a problem with promises.

Suggestion 1: Create a variable outside

var page

getPage()  
  .then(function(pageIn) {
    page = pageIn
    return page.getSite()
  })
  .then(function(site) {
    // …do what you intended with page and site…
  })

First of all, I dislike the ugliness of having to receive pageIn where you really want page

You could end up with a bunch of variables declared far above the actual usage, and you can't break out the function and place it in another place - it wouldn't know about page. Refactoring this code when it grows will give you more pain than you need.

Suggestion 2: Embrace "the Pyramid"

getPage()  
  .then(function(page) {
    page.getSite()
      .then(function(site) {
        // …do what you intended with page and site…
      })
  })

This happened to be Nolan Lawsson's preferred variant. I don't like it; one big purpose of promises is to avoid nesting.

This would also hinder refactoring, forcing you to make a separate call inside the closure.

Solution: Pay it forward

Using spread, you can pass on an array with a mix of values and promises:

getPage()  
  .then(function(page) {
    return [page, page.getSite()]
  })
  .spread(function(page, site) {
    // …do what you intended with page and site…
  })

This is my currently preferred syntax, but in some cases it seems like a programming language where you need to explicitly pass on every variable you need to use for the next statement is a bit cumbersome.

But, every function gets the arguments that it will work with. You could easily refactor this and break out the last closure:

function doIntendedThings(page, site) {  
  // …perhaps placing this in another file altogether…
}

getPage()  
  .then(function(page) {
    return [page, page.getSite()]
  })
  .spread(doIntendedThings)

You can see a real usage of this in some of the unit-tests of Some comments: models.js line 88-109