I’ve used promises on every Node project I’ve worked on over the past year. I’ve made a lot of mistakes and done a fair bit of reading on the subject. The following is a guide to writing clear, concise functional code with the Bluebird promise library. Additionally, I’ll use the classic promise syntax with chained methods, rather than async/await. In my opinion, it’s easier to read. However, I like to think that what I have to say will transfer well to other promise libraries and the async/await syntax.

1/19/2018 – I’ve updated this article to talk a little about Bluebird’s Promise.map in the Most Useful Methods section.

Article Parts

  1. For Absolute Beginners: Why Promises?
  2. Most Useful Methods in Bluebird
  3. Basic Structure of a Multi-Promise Call
  4. Problems and Anti-Patterns (with Suggestions)
  5. Separating Concerns: Readable Logic with Promise Chains
  6. References

For Absolute Beginners: Why Promises?

If you’re brand new to coding or Javascript, one thing to know is that Javascript is designed to be asynchronous. Logic starts to run in the order that it’s written, but when a function doesn’t return/complete instantly, the following code runs instantly without waiting for the return/completion of its predecessor. A simple example of this is a timeout.

[pastacode lang=”javascript” manual=”var%20willRunFirst%20%3D%20function()%20%7B%0A%20%20console.log(‘bar’)%3B%0A%7D%0Avar%20willRunSecond%20%3D%20function()%20%7B%0A%20%20console.log(‘foo’)%3B%0A%7D%0A%0AsetTimeout(willRunSecond%2C%202000)%3B%0AwillRunFirst()%3B” message=”” highlight=”” provider=”manual”/]

The output for the above is:

[pastacode lang=”javascript” manual=”bar%0Afoo” message=”” highlight=”” provider=”manual”/]

Because willRunSecond is delayed by two seconds, willRunFirst‘s console log finishes first and prints first, even though willRunSecond appears in the code before it.

So in traditional Javascript, code that must run after a value is returned are placed inside of callbacks, which don’t run until the value is returned. Often, but not always, the value that the callback needs comes from an HTTP request (asking for data from another resource, like the server or an external API). Here’s an example of a traditional browser Javascript (jQuery) callback requesting an external resource:

[pastacode lang=”javascript” manual=”%24.ajax(%7B%0A%20%20url%3A%20%22%2Fsend%2Fstuff%22%2C%0A%20%20method%3A%20%22POST%22%2C%0A%20%20data%3A%20%7B%0A%20%20%20%20name%3A%20%22Sally%22%2C%0A%20%20%20%20dob%3A%20%221925%2F05%2F29%22%0A%20%20%7D%2C%0A%20%20success%3A%20function(response)%20%7B%0A%20%20%20%20console.log(‘success%3A%20’%20%2B%20response)%0A%20%20%7D%2C%0A%20%20error%3A%20function(err)%20%7B%0A%20%20%20%20console.log(‘error%3A%20’%20%2B%20err)%3B%0A%20%20%7D%0A%7D)%3B” message=”single $.ajax request without promises” highlight=”” provider=”manual”/]

Pretty clear, right? But what if you need to make another HTTP request using a value that you calculate based on the result from the first HTTP request? Because callbacks are nested, they can get messy pretty quickly. It can be unclear what your logic is actually doing, and closing parentheses are hard to keep track of. Awesome code not only runs efficiently, but is also easily readable by other humans. So something like the function below really sucks for everybody — for you writing it, and for others reading it.

[pastacode lang=”javascript” manual=”%24.ajax(%7B%0A%20%20url%3A%20%22%2Fsend%2Fstuff%22%2C%0A%20%20method%3A%20%22POST%22%2C%0A%20%20data%3A%20%7B%0A%20%20%20%20name%3A%20%22Sally%22%2C%0A%20%20%20%20dob%3A%20%221925%2F05%2F29%22%0A%20%20%7D%2C%0A%20%20success%3A%20function(response)%20%7B%0A%20%20%20%20console.log(‘success%3A%20’%20%2B%20response)%0A%20%20%20%20%24.ajax(%7B%0A%20%20%20%20%20%20url%3A%20%22%2Fsend%2Fmore%2Fstuff%22%2C%0A%20%20%20%20%20%20method%3A%20%22POST%22%2C%0A%20%20%20%20%20%20data%3A%20%7B%0A%20%20%20%20%20%20%20%20name%3A%20%22Sally%22%2C%0A%20%20%20%20%20%20%20%20birthCity%3A%20response.city%0A%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20success%3A%20function(nextResponse)%20%7B%0A%20%20%20%20%20%20%20%20console.log(‘success%3A%20’%20%2B%20nextResponse)%0A%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20error%3A%20function(err)%20%7B%0A%20%20%20%20%20%20%20%20console.log(‘error%3A%20’%20%2B%20err)%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D)%3B%0A%20%20%7D%2C%0A%20%20error%3A%20function(err)%20%7B%0A%20%20%20%20console.log(‘error%3A%20’%20%2B%20err)%3B%0A%20%20%7D%0A%7D)%3B” message=”double $.ajax request without promises” highlight=”” provider=”manual”/]

This is where promises come in handy. jQuery supports promises in ajax calls based on its wrapper around native Javascript promises. Suddenly, the above can be refactored like this:

[pastacode lang=”javascript” manual=”%24.ajax(%7B%0A%20%20url%3A%20%22%2Fsend%2Fstuff%22%2C%0A%20%20method%3A%20%22POST%22%2C%0A%20%20data%3A%20%7B%0A%20%20%20%20name%3A%20%22Sally%22%2C%0A%20%20%20%20dob%3A%20%221925%2F05%2F29%22%0A%20%20%7D%0A%7D)%0A.then(function(response)%20%7B%0A%20%20console.log(‘success%3A%20’%20%2B%20response)%0A%20%20return%20%24.ajax(%7B%0A%20%20%20%20url%3A%20%22%2Fsend%2Fmore%2Fstuff%22%2C%0A%20%20%20%20method%3A%20%22POST%22%2C%0A%20%20%20%20data%3A%20%7B%0A%20%20%20%20%20%20name%3A%20%22Sally%22%2C%0A%20%20%20%20%20%20birthCity%3A%20response.city%0A%20%20%20%20%7D%0A%20%20%7D)%0A%7D)%0A.done(function(nextResponse)%20%7B%0A%20%20console.log(‘success%3A%20’%20%2B%20nextResponse)%0A%7D)%0A.fail(function(err)%20%7B%0A%20%20console.log(‘error%3A%20’%20%2B%20err)%3B%0A%7D)%3B” message=”double $.ajax request with promises” highlight=”” provider=”manual”/]

Much more readable, right? With native Javascript promises, you must create/define a new Promise instance (promise-returning function) before calling it, like the following vanilla XHR request:

[pastacode lang=”javascript” manual=”var%20request%20%3D%20function(method%2C%20path%2C%20data)%20%7B%0A%20%20return%20new%20Promise(function(resolve%2C%20reject)%20%7B%0A%20%20%20%20var%20req%20%3D%20new%20XMLHttpRequest()%3B%0A%20%20%20%20req.open(method%2C%20path)%3B%0A%20%20%20%20req.setRequestHeader(%22Content-Type%22%2C%20%22application%2Fjson%3Bcharset%3DUTF-8%22)%3B%0A%20%20%20%20req.onload%20%3D%20function()%20%7B%0A%20%20%20%20%20%20if(req.status%20%3D%3D%20200)%20%7B%0A%20%20%20%20%20%20%20%20resolve(req.responseText)%3B%0A%20%20%20%20%20%20%7D%20else%20%7B%0A%20%20%20%20%20%20%20%20reject(new%20Error(req.statusText))%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%20%20%20%20req.onerror%20%3D%20function()%20%7B%0A%20%20%20%20%20%20reject(new%20Error(‘unknown%20error’))%3B%0A%20%20%20%20%7D%0A%20%20%20%20return%20req.send(JSON.stringify(data))%3B%0A%20%20%7D)%3B%0A%7D” message=”xhr Promise definition” highlight=”” provider=”manual”/]

[pastacode lang=”javascript” manual=”%2F%2Fwith%20some%20function%20renderMessage%0Avar%20message%3B%0Areturn%20request(‘POST’%2C%20’%2Fmail’%2C%20data)%0A.then(function(result)%20%7B%0A%20%20if(result%20%3D%3D%20’true’)%20%7B%0A%20%20%20%20message%20%3D%20’Your%20message%20was%20successfully%20sent.%20’%3B%0A%20%20%7D%20else%20%7B%0A%20%20%20%20message%20%3D%20%22Sorry%2C%20we%20couldn’t%20send%20your%20message.%20Try%20again%20later.%20%22%3B%0A%20%20%7D%0A%20%20renderMessage(message)%3B%0A%7D)%0A.catch(function(error)%20%7B%0A%20%20message%20%3D%20%22Sorry%2C%20we%20couldn’t%20send%20your%20message.%20Try%20again%20later.%20%22%3B%0A%20%20renderMessage(message)%3B%0A%7D)%3B” message=”xhr Promise call” highlight=”” provider=”manual”/]

With the Bluebird library, we can “promisify” callback-returning functions and construct promise chains on demand without explicit definitions. This is not the only way to use a promise library, but I find it useful and concise, especially for the server. The rest of this article will focus on writing functional promises using the Bluebird library as a substitute for the native Javascript Promise class. Node packages expose a wide variety of asynchronous functions that can be “promisified”, including writing to/reading from the file system, querying a database, and sending emails and text messages. Some Node packages even expose promise-returning functions, but including Bluebird or another promise library will help you smoothly call an asynchronous function, wait for its result, transform it, and call the next asynchronous function in a chain of virtually any length, while handling errors through a single channel. I’ll talk more about why this is good later on.

Most Useful Methods in Bluebird

To get started with Bluebird on the server, replace the native Promise class with Bluebird in your file:

[pastacode lang=”javascript” manual=”const%20Promise%20%3D%20require(‘bluebird’)%3B” message=”require Bluebird” highlight=”” provider=”manual”/]

What can Bluebird do for you? Here are some of the methods I use the most in my code. I’ll give an example using all of them at the end of the section.

Promise.promisify(singleFunction) and Promise.promisifyAll(methods)

“Promisification” is the best because it allows you to use virtually any traditional callback-taking function or method (possibly from a library) and transform it into a promise-returning machine. For example, using Promise.promisifyAll(require('fs')), you can read and write from the file system and handle promises instead of callbacks. Ditto for async functions almost any library. Bluebird appends “Async” to the end of every promisified method name, which helps me personally keep track of which traditional library methods are returning promises. More from the Bluebird docs…

Promise.resolve(value)

You can chain promise after promise… but eventually, you need to return a value. Promises, however, are designed to return promises, not values. Enter Promise.resolve, which allows you to return a value from a promise. The returned value can ‘fulfill’ the promise in one of two ways: it can either exit the promise chain (returning a plain value for non-promise functions to read) or become the value returned to the next .then method block in the promise chain. If there are further .then method blocks in the promise chain, Promise.resolve will pass the returned value to the next .then method block. Only a Promise.resolve called in the last .then method block (or occasionally from a .catch method block) will return a plain value into the wild. More from the Bluebird docs…

Promise.reject(error)

Promise.reject will reject the promise and return an error object. Traditionally, the .catch method block rejects any errors, but Promise.reject allows you to kick any error (even one that your code generates explicitly) to the next available .catch method block to handle as you see fit within that .catch method block. Again, Promise.reject does not exit the promise instantly, but passes the rejected error to the next .catch method block to be dealt with as an error there. In later versions of Node, promises must be rejected with a Javascript error object. To create your own errors, be descriptive and call return Promise.reject(new Error(`some string or object`)); More on creating your own errors below, and  more on Promise.reject from the Bluebird docs…

Promise.all([promiseReturningFunction1, promiseReturningFunction2])

Promise.all allows you to call any number of promise-returning functions simultaneously, as long as you don’t depend on the return of one to call another. This can be useful for speeding up multiple database or api calls that don’t depend on each other but all return promises. However, it’s important to note that if any one of the promises in the array is rejected, none of the other promises resolve. The parameter of the .then method block that follows a Promise.all call is an array where the resolved objects are at the same indices (indexes) as the functions that produced them. More from the Bluebird docs…

Promise.map(initialArray, (item, index) => {});

Promise.map allows you to map an existing array and return an array of promise-returning functions.  In a native map, you call return to pass a value into the new array and move to the next item in the initial array.  However, if you want to generate an array of promises (perhaps you intend to call them with Promise.all), a native map has the unintended consequence of executing each promise immediately and returning nothing into the array. Promise.map to the rescue! More from the Bluebird docs…

For a sandbox example of all these methods, clone the repo for this post and run experiment.js in node. This sandbox example is FULL of problems and anti-patterns, but it gives some idea of the use and limitations of each of these handy methods using just Bluebird and the fs module.

Basic Structure of a Multi-Promise Call

Most promise chains should take a single .catch method at the end to handle and reject any errors the async functions throw. You’ll notice that the parameter referencing the returned value from each promise-returning function is scoped to the .then method block — that is, you can theoretically call the returned value the same thing in every .then method block. Always return your Promise.methods and promise-returning functions! Any promise method or promise-returning function called without being explicitly returned will hang and return nothing. The exceptions to this are functions and methods from promise-returning libraries (such as axios, mongoose, sequelize). However, for clarity at even moderate scale, these calls should be wrapped in their own helper functions and thus will have to be returned on call in the “master promise”.

[pastacode lang=”javascript” manual=”return%20thisFunctionAsync(argument)%0A.then((returnedValue)%20%3D%3E%20%7B%0A%20%20%2F%2Ftransform%20returnedValue%20and%20pass%20calculatedValue%20to%20the%20next%20promise%0A%20%20return%20nextFunctionAsync(calculatedValue)%0A%7D)%0A.then((returnedValue)%20%3D%3E%20%7B%0A%20%20%2F%2Ftransform%20returnedValue%20and%20extract%20what%20you%20want%2C%20resolving%20the%20promise%0A%7D)%0A.catch((error)%20%3D%3E%20%7B%0A%20%20%2F%2Fhandle%20error%2C%20reject%20Promise%0A%7D)%3B” message=”Basic structure” highlight=”” provider=”manual”/]

Problems and Anti-Patterns (with Suggestions)

Please see the references section below for great descriptions of anti-patterns from other developers.

Nesting Promises

The whole point of promises is to flatten the “callback waterfall” for multiple nested callbacks and handle all errors in a single channel. Promises should be easy to read and never indented more than 3-4 tabs over, period. If you find yourself indenting a ton, see the section on Master Promises below.

But! you may say — I have to chain the promise directly after the function that returns it! Not true! You can perfectly well call a promise-returning function within a .then method block from a previous promise-returning function — then close the first .then method block and open the next one in series, not nested. As long as the promise-returning function is the last thing called in the .then method block, Bluebird/Javascript knows that the next .then method block in series handles its return value.

[pastacode lang=”javascript” manual=”return%20thisFunctionAsync(argument)%0A.then((returnedValue)%20%3D%3E%20%7B%0A%20%20%2F%2Ftransform%20returnedValue%20and%20pass%20calculatedValue%20to%20the%20next%20promise%0A%20%20return%20nextFunctionAsync(calculatedValue)%0A%20%20.then((nextReturnedValue)%20%3D%3E%20%7B%0A%20%20%20%20%2F%2Fhandle%20the%20return%20value%20from%20nextFunctionAsync%0A%20%20%7D)%0A%7D)%0A.catch…” message=”This is bad (nested promise)” highlight=”” provider=”manual”/]

[pastacode lang=”javascript” manual=”return%20thisFunctionAsync(argument)%0A.then((returnedValue)%20%3D%3E%20%7B%0A%20%20%2F%2Ftransform%20returnedValue%20and%20pass%20calculatedValue%20to%20the%20next%20promise%0A%20%20return%20nextFunctionAsync(calculatedValue)%0A%7D)%0A.then((returnedValue)%20%3D%3E%20%7B%0A%20%20%2F%2Fhandle%20the%20return%20value%20from%20nextFunctionAsync%0A%7D)%0A.catch…” message=”This is better (formerly nested promise)” highlight=”” provider=”manual”/]

But! you may say. The scoping! I need access to the return value of the first promise-returning function still in the .then method block of the second returning function! Okay: define a variable outside the promise chain and set the return value or some transformation of it equal, so you can access it in the second .then method block.

[pastacode lang=”javascript” manual=”return%20thisFunctionAsync(argument)%0A.then((returnedValue)%20%3D%3E%20%7B%0A%20%20%2F%2Ftransform%20returnedValue%20and%20pass%20calculatedValue%20to%20the%20next%20promise%0A%20%20return%20nextFunctionAsync(calculatedValue)%0A%20%20.then((nextReturnedValue)%20%3D%3E%20%7B%0A%20%20%20%2F%2FI%20need%20access%20to%20returnedValue%20here%20but%20I’m%20not%20supposed%20to%20nest!%20Help!%0A%20%20%7D)%0A%7D)%0A.catch…” message=”This is bad (nesting for scope reasons)” highlight=”6″ provider=”manual”/]

[pastacode lang=”javascript” manual=”let%20someVariable%3B%0Areturn%20thisFunctionAsync(argument)%0A.then((returnedValue)%20%3D%3E%20%7B%0A%20%20someVariable%20%3D%20returnedValue%3B%0A%20%20%2F%2Ftransform%20returnedValue%20and%20pass%20calculatedValue%20to%20the%20next%20promise%0A%20%20return%20nextFunctionAsync(calculatedValue)%0A%7D)%0A.then((returnedValue)%20%3D%3E%20%7B%0A%20%20%2F%2Fnow%20you%20have%20access%20via%20someVariable!%0A%7D)%0A.catch…” message=”This is better (scope resolved, formerly nested)” highlight=”1,4″ provider=”manual”/]

 

Returning a Bunch of Plain Objects Back into the Promise Chain

This anti-pattern is an abuse of Promise.resolve and usually happens when you spread promise logic over several functions instead of compartmentalizing promise-returning functions and functions to map the data they return separately.

[pastacode lang=”javascript” manual=”%2F%2Fusing%20mongoose%2C%20a%20promise-returning%20mongodb%20ORM%2C%20to%20get%20all%20the%20users%20using%20a%20room%20that%20the%20first%20user%20is%20assigned%20to%0Aconst%20callUsersDb%20%3D%20function(id)%20%7B%0A%20%20Users%0A%20%20.findById(id)%0A%20%20.select(‘name%20roomId’)%0A%20%20.then((result)%20%3D%3E%20%7B%0A%20%20%20%20const%20roomId%20%3D%20result.roomId%3B%0A%20%20%20%20return%20Promise.resolve(roomId)%3B%0A%20%20%7D)%0A%20%20.catch((err)%20%3D%3E%20%7B%0A%20%20%20%20return%20Promise.reject(err)%3B%0A%20%20%7D)%3B%0A%7D%0A%0Aconst%20callRoomsDb%20%3D%20function(id)%20%7B%0A%20%20Rooms%0A%20%20.findById(id)%0A%20%20.select(‘users’)%0A%20%20.then((result)%20%3D%3E%20%7B%0A%20%20%20%20const%20roomUsers%20%3D%20%7B%0A%20%20%20%20%20%20roomId%3A%20id%2C%0A%20%20%20%20%20%20usersArray%3A%20result%0A%20%20%20%20%7D%0A%20%20%20%20return%20Promise.resolve(roomUsers)%3B%0A%20%20%7D)%0A%20%20.catch((err)%20%3D%3E%20%7B%0A%20%20%20%20return%20Promise.reject(err)%3B%0A%20%20%7D)%3B%0A%7D%0A%0A%2F%2Ffunction%20called%20by%20controller%0Areturn%20callUsersDb(1234567890)%0A.then((result)%20%3D%3E%20%7B%0A%20%20return%20callRoomsDb(result)%3B%0A%7D)%0A.catch…” message=”This is bad (small promises return plain objects into bigger promise)” highlight=”31-36″ provider=”manual”/]

It is needlessly verbose and logically indirect to resolve the roomId value in callUsersDb just to pipe it to another promise. If these functions are spread out across files, it’s hard to get a sense of what is actually happening in the function called by the controller. For an alternative philosophy, see the section on Master Promises below.

Creating Non-Specific Errors or Rejecting Without A Native Error Object

Lots of developers will advise you to never, ever create your own Error objects. I disagree. Throwing self-created errors intentionally in promises can quickly reveal problems with database and API calls and allow you to exit a promise chain without too much bullshit (more on that under …Exiting the Chain below. But your code will quickly turn to undebuggable shit if you don’t get super specific with the errors you intentionally throw. Additionally, make sure you wrap your string/object in an instance of the Javascript Error constructor, or Node 8+ will be unhappy, and so will you.

[pastacode lang=”javascript” manual=”return%20thisFunctionAsync(argument)%0A.then((returnedValue)%20%3D%3E%20%7B%0A%20%20if(!returnedValue)%20%7B%0A%20%20%20%20return%20Promise.reject(%60it%20didn’t%20work%2C%20%3A(%60)%3B%0A%20%20%7D%20else%20%7B%0A%20%20%20%20return%20Promise.resolve(returnedValue)%3B%0A%20%20%7D%0A%7D)%0A.catch…” message=”This is very bad (reject without JS Error object)” highlight=”4″ provider=”manual”/]

[pastacode lang=”javascript” manual=”return%20thisFunctionAsync(argument)%0A.then((returnedValue)%20%3D%3E%20%7B%0A%20%20if(!returnedValue)%20%7B%0A%20%20%20%20return%20Promise.reject(new%20Error(%60it%20didn’t%20work%2C%20%3A(%60))%3B%0A%20%20%7D%20else%20%7B%0A%20%20%20%20return%20Promise.resolve(returnedValue)%3B%0A%20%20%7D%0A%7D)%0A.catch…” message=”This is still pretty bad (non-specific JS Error obj)” highlight=”4″ provider=”manual”/]

[pastacode lang=”javascript” manual=”return%20thisFunctionAsync(argument)%0A.then((returnedValue)%20%3D%3E%20%7B%0A%20%20if(!returnedValue)%20%7B%0A%20%20%20%20return%20Promise.reject(new%20Error(%7BError%3A%20%60thisFunctionAsync%20didn’t%20throw%20an%20error%20but%20returnedValue%20returned%20%24%7BreturnedValue%7D%60%7D))%3B%0A%20%20%7D%20else%20%7B%0A%20%20%20%20return%20Promise.resolve(returnedValue)%3B%0A%20%20%7D%0A%7D)%0A.catch…” message=”This is better (more specific JS Error obj)” highlight=”4″ provider=”manual”/]

Including an Error Parameter in a .then Method Block

This anti-pattern is also filed under “why bother with promises, then?” One of the best features of promises is that all your errors flow through the single catch statement. If you treat promises as glorified callbacks and include the error parameter in the .then method block, you’re messing with function-thrown error handling logic in your .then. Why do this to yourself? It’s hard to read, harder to debug, and chances are you aren’t exiting (resolving/rejecting) the promise properly.

Using defer, Ever

I don’t have much to say about this except don’t do it. I haven’t heard an example yet where the benefits of doing this outweighed the costs. If you have, please, enlighten me in the comments. For more on what this is, see the Bluebird Docs on Anti-Patterns.

Express: Failing to Return a Promise on Send/Render

This is a common mistake. In fact, almost no examples of promises with Express on Stack Overflow and like get this one right. After you render and or send your response from Express, resolve or reject the promise. It seems like a small detail, but your tests will randomly fail, and you will cry. Never leave your promises hanging.

Express: Sending/Rendering Early & Exiting the Chain

I’m going to suggest something that could be considered controversial. There’s a known problem with exiting a promise chain with n .then method blocks after executing (n – 1) or (n – 2) of them. The tedious (and test error-causing way) is to pass shit like return Promise.resolve('already rendered') to each successive block and skip the logic of that block to pass the same statement until the promise is over. I don’t like it. It’s messy, ugly, and verbose. It requires manual adjustment of every single .then method block with (more) if blocks after you render. So why not Promise.reject essentially the same thing, and handle the ifs once in the .catch method block? Sneaky, I know. But until there’s a fix for this (Promise.exit, Bluebird/ES people?), I like it better.

[pastacode lang=”javascript” manual=”var%20expressController%20%3D%20(req%2C%20res)%20%3D%3E%20%7B%0A%20%20return%20thisFunctionAsync(argument)%0A%20%20.then((returnedValue)%20%3D%3E%20%7B%0A%20%20%20%20if(!returnedValue)%20%7B%0A%20%20%20%20%20%20return%20Promise.reject(new%20Error(%7BError%3A%20%60thisFunctionAsync%20didn’t%20throw%20an%20error%20but%20returnedValue%20returned%20%24%7BreturnedValue%7D%60%7D))%3B%0A%20%20%20%20%7D%20else%20if(returnedValue%20%3D%3D%3D%20’foo’)%20%7B%0A%20%20%20%20%20%20return%20Promise.resolve(returnedValue)%3B%0A%20%20%20%20%7D%20else%20%7B%0A%20%20%20%20%20%20return%20Promise.resolve(%22first%20value%20isn’t%20foo%22)%3B%0A%20%20%20%20%7D%0A%20%20%7D)%0A%20%20.then((returnedValue)%20%3D%3E%20%7B%0A%20%20%20%20if(returnedValue%20%3D%3D%3D%20%22first%20value%20isn’t%20foo%22)%20%7B%0A%20%20%20%20%20%20return%20Promise.resolve(returnedValue)%3B%0A%20%20%20%20%7D%20else%20%7B%0A%20%20%20%20%20%20%2F%2Fdo%20something%20else%0A%20%20%20%20%7D%0A%20%20%7D)%0A%20%20.then((returnedValue)%20%3D%3E%20%7B%0A%20%20%20%20if(returnedValue%20%3D%3D%3D%20%22first%20value%20isn’t%20foo%22)%20%7B%0A%20%20%20%20%20%20res.json(%7B%0A%20%20%20%20%20%20%20%20failure%3A%20%22value%20isn’t%20foo%22%0A%20%20%20%20%20%20%7D)%3B%0A%20%20%20%20%20%20return%20Promise.resolve(true)%3B%0A%20%20%20%20%7D%20else%20%7B%0A%20%20%20%20%20%20%2F%2Fdo%20something%20else%0A%20%20%20%20%7D%0A%20%20%7D)%0A%20%20.catch…%0A%7D” message=”I don’t like this (early return stairstep)” highlight=”9,13-14,20-24″ provider=”manual”/]

[pastacode lang=”javascript” manual=”var%20expressController%20%3D%20(req%2C%20res)%20%3D%3E%20%7B%0A%20%20return%20thisFunctionAsync(argument)%0A%20%20.then((returnedValue)%20%3D%3E%20%7B%0A%20%20%20%20if(!returnedValue)%20%7B%0A%20%20%20%20%20%20return%20Promise.reject(new%20Error(%60thisFunctionAsync%20didn’t%20throw%20an%20error%20but%20returnedValue%20returned%20%24%7BreturnedValue%7D%60))%3B%0A%20%20%20%20%7D%20else%20if(returnedValue%20%3D%3D%3D%20’foo’)%20%7B%0A%20%20%20%20%20%20return%20Promise.resolve(returnedValue)%3B%0A%20%20%20%20%7D%20else%20%7B%0A%20%20%20%20%20%20res.json(%7B%0A%20%20%20%20%20%20%20%20failure%3A%20%22value%20isn’t%20foo%22%0A%20%20%20%20%20%20%7D)%3B%0A%20%20%20%20%20%20return%20Promise.reject(new%20Error(%22already%20sent%20value%20isn’t%20foo%22))%3B%0A%20%20%20%20%7D%0A%20%20%7D)%0A%20%20.then((returnedValue)%20%3D%3E%20%7B%0A%20%20%20%20%2F%2Fdo%20more%20stuff%0A%20%20%7D)%0A%20%20.then((returnedValue)%20%3D%3E%20%7B%0A%20%20%20%20%2F%2Fand%20more%20stuff%0A%20%20%7D)%0A%20%20.catch((err)%20%3D%3E%20%7B%0A%20%20%20%20if(err.message%20%3D%3D%3D%20%22already%20sent%20value%20isn’t%20foo%22)%20%7B%0A%20%20%20%20%20%20return%20Promise.resolve(true)%3B%0A%20%20%20%20%7D%20else%20%7B%0A%20%20%20%20%20%20%2F%2Fhandle%20real%20errors%0A%20%20%20%20%7D%0A%20%20%7D)%3B%0A%7D” message=”I like this better (early return with fake error)” highlight=”9-12,22,23″ provider=”manual”/]

Separating Concerns: Readable Logic with Promise Chains

Master Promises: a Locus of Logic

I’m going to use Express as an example here, but my points about abstraction probably hold for any framework. This is my opinion, and as you’ll see below, there are some qualified exceptions.

Express was the first back-end framework I learned. In all the example projects I did, it was hard for me to see why the server logic needed to be abstracted from the connection, why routers needed to be abstracted from the server logic, and why the controller functions needed to be abstracted from the routes. My first apps were a hot mess because I didn’t grasp the concept of readability at scale. No one wants a single thousand line Javascript file with tons of repeating code blocks unless it’s compiled by Webpack. But the truth is that I hadn’t gone far enough. Your controller function should be merely a road map of the logic that powers sending your response. In the land of async functions, that roadmap should be one crystal clear promise chain without any smaller promise chains embedded in it. For the “don’t” example, see the Returning a Bunch of Plain Objects Back into the Promise Chain anti-pattern above.

The key to readable master promises is to wrap every promise-returning function and each transformation of the returned data in its own helper function. You might have 10-15 of these tidbits per helper file, and you can organize them however you want (database queries in a models subdirectory, etc). When you’re done, the master promise is a summary of what you’re doing, and the actual logic of it is tucked away into helper functions, which can be reused (and reused) depending on how abstract you can readably make their parameters.

Here’s an Express example using sessions. It collects some data about a potential assignment for some user from the client, and queries or writes to the users, assignments, and rooms collections or tables. This example only reads/writes from the database, but you can use this same approach to execute/return any async function.

[pastacode lang=”javascript” manual=”const%20controller%20%3D%20(req%2C%20res)%20%3D%3E%20%7B%0A%20%20let%20user%3B%0A%20%20let%20assignment%3B%0A%20%20return%20queryUser(req.body.id)%0A%20%20.then((userResult)%20%3D%3E%20%7B%0A%20%20%20%20console.log(userResult)%3B%0A%20%20%20%20if(userResult)%20%7B%0A%20%20%20%20%20%20user%20%3D%20userToRenderInfo(userResult)%3B%0A%20%20%20%20%20%20const%20getAssnInfo%20%3D%20userToAssn(userResult%2C%20req.body.someThing)%3B%0A%20%20%20%20%20%20return%20writeAssignment(getAssnInfo)%0A%20%20%20%20%7D%20else%20%7B%0A%20%20%20%20%20%20req.session.error%20%3D%20%60No%20user%20exists%20with%20id%20%24%7Breq.body.id%7D%60%3B%0A%20%20%20%20%20%20return%20Promise.reject(new%20Error(req.session.error))%3B%0A%20%20%20%20%7D%0A%20%20%7D)%0A%20%20.then((newAssn)%20%3D%3E%20%7B%0A%20%20%20%20console.log(newAssn)%3B%0A%20%20%20%20if(newAssn)%20%7B%0A%20%20%20%20%20%20assignment%20%3D%20assnToRenderInfo(newAssn)%3B%0A%20%20%20%20%20%20const%20getRoomInfo%20%3D%20assnToRoom(newAssn)%3B%0A%20%20%20%20%20%20return%20queryRoom(getRoomInfo)%0A%20%20%20%20%7D%20else%20%7B%0A%20%20%20%20%20%20req.session.error%20%3D%20%60No%20assignment%20was%20created%20for%20user%20id%20%24%7Breq.body.id%7D%20with%20some%20detail%20%24%7Breq.body.someThing%7D%60%3B%0A%20%20%20%20%20%20return%20Promise.reject(new%20Error(req.session.error))%3B%0A%20%20%20%20%7D%0A%20%20%7D)%0A%20%20.then((roomResult)%20%3D%3E%20%7B%0A%20%20%20%20console.log(roomResult)%3B%0A%20%20%20%20if(roomResult)%20%7B%0A%20%20%20%20%20%20const%20rooms%20%3D%20roomToRenderInfo(roomResult)%3B%0A%20%20%20%20%20%20req.session.success%20%3D%20%60Assignment%20scheduled%20and%20available%20rooms%20successfully%20retrieved%60%3B%0A%20%20%20%20%20%20res.json(%7B%0A%20%20%20%20%20%20%20%20success%3A%20req.session.success%2C%0A%20%20%20%20%20%20%20%20user%2C%0A%20%20%20%20%20%20%20%20assignment%2C%0A%20%20%20%20%20%20%20%20rooms%0A%20%20%20%20%20%20%7D)%3B%0A%20%20%20%20%20%20return%20Promise.resolve(req.session.success)%3B%0A%20%20%20%20%7D%20else%20%7B%0A%20%20%20%20%20%20req.session.error%20%3D%20%60No%20rooms%20returned%20for%20assignment%20%24%7Bassignment%7D%20for%20user%20id%20%24%7Breq.body.id%7D%60%3B%0A%20%20%20%20%20%20return%20Promise.reject(new%20Error(req.session.error))%3B%0A%20%20%20%20%7D%0A%20%20%7D)%0A%20%20.catch((err)%20%3D%3E%20%7B%0A%20%20%20%20if(!req.session.error)%20%7B%0A%20%20%20%20%20%20req.session.error%20%3D%20%60Failed%20to%20schedule%20user%20assignment%20for%20user%20id%20%24%7Breq.body.id%7D%20with%20some%20detail%20%24%7Breq.body.someThing%7D%60%3B%0A%20%20%20%20%7D%0A%20%20%20%20res.json(%7B%0A%20%20%20%20%20%20error%3A%20req.session.error%0A%20%20%20%20%7D)%3B%0A%20%20%20%20return%20Promise.reject(err)%3B%0A%20%20%7D)%3B%0A%7D” message=”Master promises example” highlight=”” provider=”manual”/]

[pastacode lang=”javascript” manual=”%2F%2Fvariables%20scoped%20to%20the%20controller%20function%20are%20declared%20and%20later%20assigned%20to%20the%20objects%20holding%20the%20info%20we%20want%20to%20render%2Fsend%3A%0Auser%0Aassignment%20%0A%0A%2F%2Fhelper%20functions%0A%0A%2F%2Fplain-object-returning%20functions%20that%20map%20db%20responses%20to%20objects%20to%20render%2Fsend%0AuserToRenderInfo%0AassnToRenderInfo%0AroomToRenderInfo%0A%0A%2F%2Fplain-object-returning%20functions%20that%20map%20db%20responses%20to%20extract%20data%20for%20another%20CRUD%20operation%20(eg%2C%20the%20next%20promise-returning%20function%20you%20want%20to%20call)%0AuserToAssn%0AassnToRoom%0A%0A%2F%2Fpromise-returning%20functions%20that%20CRUD%20our%20db%20(see%20mongoose%2C%20sequelize%2C%20etc)%0AqueryUser%0AwriteAssignment%0AqueryRoom” message=”List of helper functions and scoped variables” highlight=”1,7,12,16″ provider=”manual”/]

Obviously there are more ways that you might want to map data, but the idea is to define all transactional logic in helper functions with return values and call them in your controller. Some of these helper functions will return promises and some plain objects. All of these helper functions will be located in other files. This also helps hierarchicalize your requires, reducing your chances of having to untangle circular dependencies. Model files are only included in that model’s query files; only needed queries and mapping functions need be required in the controller. If you’re defining functions in your controller, it’s time to rethink that.

When and How To Nest

Given all of the above, is there every an appropriate time to nest? I argue that there can be, when a promise chain has two very clear loci (locuses) of logic. What I mean by nesting is this section is including one inner promise chain as a promise-returning function called in an outer promise chain that contains it. I ran into a situation recently where I found that I preferred using a nested promise: validating a user with the Passportjs local strategy.

On the Passport docs site (as of October 2017), they show a local strategy querying/writing to a database with a callback and returning done. However, in practice, the logic of validation is invariably more complicated. For example, on this project, the steps to signup a user were

  1. Check if the signup token is valid (if not, return);
  2. Check to see if the user already exists (if so, return);
  3. If the new user is associated with a new business, create the new business;
  4. Add the new business ID to the user, if any, and save the new user;
  5. Update all associated businesses, new or not, with the new user’s ID;
  6. Mark the signup token used

So in this case, I decided to use two promise chains to show two main focuses of the logic — doing the database operations to save the user, and using Passport to authenticate. Here’s what the passport strategy ended up looking like:

[pastacode lang=”javascript” manual=”const%20signupStrategy%20%3D%20new%20LocalStrategy(%7B%0A%20%20%2F%2Foptions%20object%0A%20%20%2F%2Fpass%20the%20request%20object%20from%20express%20to%20the%20auth%20function%20(needed%20to%20access%20the%20values%20the%20user%20put%20in)%0A%20%20passReqToCallback%3A%20true%2C%0A%20%20%2F%2Ftell%20the%20auth%20function%20that%20req.body.email%20is%20the%20username%0A%20%20usernameField%3A%20’email’%2C%0A%20%20%2F%2Ftell%20the%20auth%20function%20that%20req.body.password%20is%20the%20password%0A%20%20passwordField%3A%20’password’%0A%7D%2C%20(req%2C%20username%2C%20password%2C%20done)%20%3D%3E%20%7B%0A%20%20%2F%2Fsignup%20strategy%20logic%20function%0A%20%20console.log(req.body%2C%20req.params)%3B%0A%20%20%2F%2Flook%20up%20the%20signup%20token%20in%20the%20db%20to%20verify%20that%20the%20token%20exists%2C%20is%20authorized%2C%20and%20is%20not%20expired%0A%20%20if(password.length%20%3C%208)%20%7B%20%0A%20%20%20%20req.session.error%20%3D%20’Sorry%2C%20your%20password%20is%20too%20short.%20Please%20make%20it%20longer.’%3B%20%0A%09%2F%2Freturn%20false%20for%20registration%20back%20to%20the%20signup%20strategy%20return%20%0A%09done(null%2C%20false)%3B%20%0A%20%20%7D%20else%20%7B%20%0A%20%20%20%20%2F%2FFIRST%20MAJOR%20PROMISE%20CHAIN%20handles%20passport%20auth%20logic%20%0A%20%20%20%20%2F%2FSECOND%20MAJOR%20PROMISE%20CHAIN%20handles%20signup%20db%20logic%20%0A%20%20%20%20return%20signup(req)%0A%09.then((sessionUser)%20%3D%3E%20%7B%0A%20%20%20%20%20%20console.log(%60registration%20result%20%24%7BsessionUser%7D%60)%3B%0A%20%20%20%20%20%20%2F%2Fget%20a%20user%20session%20object%20or%20false%20from%20the%20registration%20function%0A%20%20%20%20%20%20if(sessionUser)%20%7B%0A%20%20%20%20%20%20%20%20%2F%2Fif%20the%20the%20user%20was%20successfully%20returned%20from%20saving%2C%20log%20the%20success%20message%20and%20pass%20true%20to%20done%20to%20authorize%20(log%20in)%20the%20user%0A%20%20%20%20%20%20%20%20console.log(req.session.success)%3B%0A%20%20%20%20%20%20%20%20return%20Promise.resolve(done(null%2C%20sessionUser))%3B%0A%20%20%20%20%20%20%7D%20else%20%7B%0A%20%20%20%20%20%20%20%20%2F%2Fif%20the%20user%20obj%20is%20false%20and%20there%20is%20an%20error%20message%2C%20log%20the%20error%20message%20and%20reject%20promise%20to%20catch%20method%20below%0A%20%20%20%20%20%20%20%20if(!req.session.error)%20%7B%0A%20%20%20%20%20%20%20%20%20%20req.session.error%20%3D%20%22there’s%20no%20session%20user%20from%20signup%20logic%22%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20return%20Promise.reject(new%20Error(req.session.error))%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D)%0A%20%20%20%20.catch((err)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%2F%2Fif%20something%20goes%20wrong%20with%20the%20auth%20process%2C%20log%20the%20error%20and%20pass%20false%20to%20done%20to%20say%20that%20the%20user%20wasn’t%20successfully%20signed%20up%2Flogged%20in%0A%20%20%20%20%20%20console.log(err)%3B%0A%20%20%20%20%20%20if(!req.session.error)%20%7B%0A%20%20%20%20%20%20%20%20req.session.error%20%3D%20’some%20unknown%20error%20signing%20you%20up’%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20console.log(req.session.error)%3B%0A%20%20%20%20%20%20return%20Promise.resolve(done(null%2C%20false))%3B%0A%20%20%20%20%7D)%3B%0A%20%20%7D%0A%7D)%3B” message=”Passport signup auth LocalStrategy” highlight=”19,20″ provider=”manual”/]

Note how I’m returning Promise.resolve from the .catch method block. That’s because I want to reject authorization by calling done with a false user, but to do that I need to fulfill the promise with that false done call, not reject the promise itself. Thank you, Bluebird!

That about wraps it up! More reading below.

Resources