Promise Chains + For Comprehensions

I don’t write a ton of JavaScript these days. I program mostly in Scala (with some Ruby sprinkled in).

When I was writing a lot of JavaScript a few years ago, one of my favorite patterns was the Promise chain (specifically, a bluebird Promise chain).

Promises enabled asynchronous functionality without requiring the standard JavaScript callback pattern. Promise chains were visually similar to synchronous code, in which there was no need for nesting upon nesting (aka callback hell). Each step of the chain was aligned, making reading through each step of the code easy on the eyes.

Each .then call within the Promise chain took a single argument, representing the value from the previous step in the chain. This marked a subtle but powerful difference from the callback pattern, in which functions which took two arguments: an error value (which, in the success case would be falsey) and the value of the previous callback. Including possible error values forced each step within the callback nesting to face possible error handling, limiting the ability to build a collection of smaller pure functions to reference within the callback nesting. Each step within the callback nesting needed to be aware of the greater context in order to properly handle errors (should errors raise an exception? Log something out? Ignore and continue?).

With Promise chain steps only being passed the single return value of the preceding step in the chain, writing pure functions and passing them to each .then call was possible and encouraged. It allowed the behavior of each step to be written in a separate, defined function rather than embedded within a sprawling nest of callbacks. Error handling for all the steps in the Promise chain was pushed to a single, common area, separating the concerns of the happy path and all other paths.

Today, I most write Scala. Scala has this handy pattern called a for comprehension. I find a lot of overlap between Promise chains in JavaScript and for comprehensions in Scala.

There’s a similarity between these two that might be implicit to a lot of developers that have worked both in Scala and JavaScript, though I haven’t seen it explicitly discussed (maybe this is because there aren’t many developers who have worked both in Scala and JavaScript?). Personally, I think they’re both elegant, visually appealing structures for common patterns (callback syntax in JavaScript, .map and .flatMap chaining in Scala). There’s a lot of overlap in both of what they do, their visual structure, and their prescription for program design.

They’re really both about collapsing the horizontal stretching that comes with having nested blocks of code. For JavaScript, this comes as a result of callback pattern, which deals with asynchronous code. For Scala, the primary usage is syntactic sugar for .map and .flatMap calls, which can be used with Scala’s Future trait for asynchronous computation but can be more generalized to monadic comprehensions.

My admiration for these flavors of syntactic sugar stems from pure visual vanity (although, isn’t that what all syntactic sugar is for?): The horizontal alignment. The hemming in of different styles into a consistent pattern. The easy, recipe-like line-by-line instruction set that comes with a well-constructed chain. The coalesced nature of error handling.

I love the basic ingredients of both the Promise chain and the for comprehension.

  • Each individual step is visually aligned with other steps, and the structure of each step is consistent. For Promise chains, it’s each .then call taking the return value of the previous step, invoking a function, and then handing that return value off to the next step. For the for comprehension, it’s named captured values on the left and commands on the right.
  • Each iteration within the chain (link, if you will) should represent a discrete step of a process. Much like a recipe, each line is an individual action but piped together they coalesce into a single result.
  • Error handling is pushed to a single location, rather than scattershot within the program. In the case of Scala, the error case is encoded in the result type of the for comprehension; for the Promise chains, exceptions will all be caught and pushed to a .catch block (and in the case of bluebird, I believe different exception types could have their own .catch blocks).

Even though built for different intents and use cases, I’m drawn to Promise chains and for comprehensions for the same reason: Both enforce a common structure (and typing) for common patterns. Both tidy up sprawling horizontal code into neat, rigid alignment. Both push error handling to a single place, separating the happy path from the unhappy path. Employing Promise chains and for comprehensions where applicable results in code that is cleaner, clearer, and more consistent.