Promise Chains + For Comprehensions
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.
.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?).
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
for comprehensions in Scala.
.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.
.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
- Each individual step is visually aligned with other steps, and the structure of each step is consistent. For
Promisechains, it’s each
.thencall taking the return value of the previous step, invoking a function, and then handing that return value off to the next step. For the
forcomprehension, 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
forcomprehension; for the
Promisechains, exceptions will all be caught and pushed to a
.catchblock (and in the case of bluebird, I believe different exception types could have their own
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.