When Nesting Promises is Correct
Tags: promises, javascript, async, scope
Published: 2019-04-20 04:01:35

---
title: When Nesting Promises is Correct
published: true
description: Solving an Async Scope Issue In Multiple Ways
tags: promises, javascript, async, scope
---

# Intro

By now, [promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) are well-established in the JS ecosystem, not only being officially specified in [ECMAScript](https://www.ecma-international.org/publications/standards/Ecma-262.htm), but even having a first-class syntactic sugar in the form of [`async`](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await) functions.

When learning promises, many JS developers are told that a major advantage of promise chaining is that it keeps the code "flat", avoiding the [pyramid of doom](https://en.wikipedia.org/wiki/Pyramid_of_doom_(programming)) of nested callbacks. While this is partly true, it also puts undue emphasis on code _appearance_, running the risk of [missing the point](https://blog.domenic.me/youre-missing-the-point-of-promises/).

True ["callback hell"](http://callbackhell.com/) is less about indentation – in fact, by naming callback functions and factoring them out to the top level, one can often flatten out async code _without_ the need for promises. Instead, callback hell is when we lose the composable vanilla function API (pass in data, receive result), where returned values can be bound to variables, aggregated in collections, passed to other functions, and combined in first-class ways.

All of this preamble is to give context to the following statement: nesting promises is _often_ an antipattern, but _not always_. In fact, there is a common situation in which a little nesting can make perfect sense, though there exist several alternatives. This short article will demonstrate a common scoping issue with promises and multiple solutions for that issue.

# The Setup

For these examples, we will imagine that the function `getPuppyById` is an AJAX method returning some data via a promise. Puppies will be objects with a `bestFriend` foreign key to another puppy:

```js
{
    id: 4,               // this puppy's id
    name: 'Mr. Wiggles', // this puppy's name
    bestFriend: 17       // id of this puppy's best friend (another puppy)
}
```

If we wish to fetch puppy #1's best friend's name, we can chain calls to `getPuppyById`:

{% runkit
const getPuppyById = (id) => new Promise((res) => {
    const time = Math.random() * 500 + 500
    setTimeout(() => res({
        1: { name: 'Floof', id: 1, bestFriend: 7 },
        7: { name: 'Rex', id: 7, bestFriend: 1 }
    }[id]), time)
})
%}
const friendNameP =
    getPuppyById(1)                               // first fetch
    .then(pup1 => getPuppyById(pup1.bestFriend))  // second fetch
    .then(friend => friend.name)                  // data transformation

friendNameP  // our goal, a promise for the best friend name
    .then(name => console.log('friend name', name))
    .catch(e => console.error(e))
{% endrunkit %}

This works just fine when our early results are just discardable steps towards our desired final result.

# The Problem

However, what if we wanted to produce a promise for _both puppies' names_ – the original and the friend? Because the callback passed to `then` introduces a function scope, the first puppy may no longer be in scope further down the chain.

{% runkit
const getPuppyById = (id) => new Promise((res) => {
    const time = Math.random() * 500 + 500
    setTimeout(() => res({
        1: { name: 'Floof', id: 1, bestFriend: 7 },
        7: { name: 'Rex', id: 7, bestFriend: 1 }
    }[id]), time)
})
%}
const twoPuppyNamesP =
    getPuppyById(1)                               // first fetch
    .then(pup1 => getPuppyById(pup1.bestFriend))  // second fetch
    .then(friend => {
        return [pup1.name, friend.name] // ERROR – pup1 no longer in scope!
    })

// DO NOT EDIT BELOW

twoPuppyNamesP  // our goal, a promise for the puppy and friend names
    .then(names => console.log('puppy names', names))
    .catch(e => console.error(e))
{% endrunkit %}

There are multiple ways to solve this, which we will examine in a moment. Before we do so, go ahead and fix the above code snippet using whatever technique _you_ may prefer. Only edit the top half of the snippet; you are trying to make `twoPuppyNamesP` fulfill its promise (hah) of delivering _both_ puppies.

# Solutions

## Library-Specific: Bluebird `bind`

Before promises became official in ES2015, third-party implementations like [Bluebird](http://bluebirdjs.com/docs/getting-started.html) were popular. Bluebird is still used by some codebases for its speed and wide array of utility methods.

Though it breaks [section 2.2.5 of the A+ promise spec](https://promisesaplus.com/#point-35) to do so, Bluebird includes a special feature in which you can set the `this` value of a promise chain – providing a shared mutable namespace in which to save intermediate results. The specific method is named [`bind`](http://bluebirdjs.com/docs/api/promise.bind.html).

> _`.bind` also has a useful side purpose - promise handlers don't need to share a function to use shared state_

{% runkit
const getPuppyById = (id) => new Promise((res) => {
    const time = Math.random() * 500 + 500
    setTimeout(() => res({
        1: { name: 'Floof', id: 1, bestFriend: 7 },
        7: { name: 'Rex', id: 7, bestFriend: 1 }
    }[id]), time)
})
%}
const P = require('bluebird')
const toBluebird = p => P.resolve(p)

const twoPuppyNamesP =
    toBluebird(getPuppyById(1))                   // first fetch
    .bind({})                                     // set 'this' for chain
    .then(function (pup1) {                       // arrows don't have 'this'
        this.pup1 = pup1                          // saving state for later
        return getPuppyById(pup1.bestFriend)      // second fetch
    })
    .then(function (friend) {
        return [this.pup1.name, friend.name]  // accessing 'pup1' in shared state
    })

twoPuppyNamesP  // our goal, a promise for the puppy and friend names
    .then(names => console.log('puppy names', names))
    .catch(e => console.error(e))
{% endrunkit %}

While this _works_, it has significant drawbacks:

- it complicates the promise chain with spec-breaking features
- it requires using `function` functions to access `this`
- it is non-portable knowledge tied to a specific library

## A+-Compliant, ECMA-Approved: `Promise.all`

If only we could pass _multiple values_ down through a promise chain – even when one of those values is a pending promise, whose value we wish to access further down the chain.

Of course, we do not need to wish for such a feature, as it is available via the [`Promise.all`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all) static method. By returning an array of both _synchronous values_ and _promise values_, wrapped in a call to `all`, we get access to an array of _synchronous values_ in the next `then`.

{% runkit
const getPuppyById = (id) => new Promise((res) => {
    const time = Math.random() * 500 + 500
    setTimeout(() => res({
        1: { name: 'Floof', id: 1, bestFriend: 7 },
        7: { name: 'Rex', id: 7, bestFriend: 1 }
    }[id]), time)
})
%}
const twoPuppyNamesP =
    getPuppyById(1)                                   // first fetch
    .then(pup1 => {
        const friendP = getPuppyById(pup1.bestFriend) // second fetch
        return Promise.all([pup1, friendP])           // collect both results
    })
    .then(([pup1, friend]) => {                       // array destructuring
        return [pup1.name, friend.name]               // data transformation
    })

twoPuppyNamesP  // our goal, a promise for the puppy and friend names
    .then(names => console.log('puppy names', names))
    .catch(e => console.error(e))
{% endrunkit %}

Even though the array passed to `.all` has a mix of normal and promise values, the resulting overall promise is for an array of normal values.

This strategy will work in any setting that supports ES2015, and is thus much more portable than the Bluebird `bind` trick. Unfortunately, it too has cons:

- more verbose return lines
- more complex function parameters and destructuring
- as the chain grows, passing down multiple results does not scale well
- overall, a lot of redundant "plumbing" of early values through the chain

## Controlled State, Shared Scope

We now come to one of the most common and viable techniques for sharing state through a promise chain – use a mutable or reassignable variable(s) in a higher scope. As each handler in a `then` chain is invoked, it will set and/or read the values of a shared `let` binding or the properties of a shared object.

{% runkit
const getPuppyById = (id) => new Promise((res) => {
    const time = Math.random() * 500 + 500
    setTimeout(() => res({
        1: { name: 'Floof', id: 1, bestFriend: 7 },
        7: { name: 'Rex', id: 7, bestFriend: 1 }
    }[id]), time)
})
%}
let pup1                                              // shared binding

const twoPuppyNamesP =
    getPuppyById(1)                                   // first fetch
    .then(gotPup1 => {
        pup1 = gotPup1                                // save state
        return getPuppyById(pup1.bestFriend)          // second fetch
    })
    .then(friend => {
        return [pup1.name, friend.name]               // data transformation
    })

twoPuppyNamesP  // our goal, a promise for the puppy and friend names
    .then(names => console.log('puppy names', names))
    .catch(e => console.error(e))
{% endrunkit %}

This may seem "illegal" considering how we normally consider async code to work, but in fact it is guaranteed to work as expected as later callbacks in a `then` chain can only be invoked after earlier callbacks. So the usage of `pup1` in the second `then` will work because `pup1` is guaranteed to have been assigned in the callback of the previous `then`.

This has some distinct advantages:

- it is relatively clear even for people without advanced knowledge of promises
- it is setting-agnostic
- it is relatively light on syntax
- the chain remains flat, reducing mental load

As always, there are still tradeoffs to consider, however.

- shared mutable state is risky; care should be taken to _only_ allow the promise chain to read or modify these variables
  - reading outside the chain is not guaranteed to work due to indeterminate timing
  - writing outside the chain can break guarantees within the chain
- we now need two versions of the variable name – a parameter name like `gotPup1` and a shared state variable like `pup1` – to avoid [shadowing](https://en.wikipedia.org/wiki/Variable_shadowing)

If the promise chain is itself contained within a short function scope, disciplined use of shared state in a local setting can be concise and easy way to solve the issue of passing information down the chain.

## The Punchline: Nested Promises

This article opened with the promise (hah) of showing a situation in which a small bit of nesting can be a valid and useful technique. The key point is that with a nested chain, an inner `then` still has scope access to the results from an outer `then`.

{% runkit
const getPuppyById = (id) => new Promise((res) => {
    const time = Math.random() * 500 + 500
    setTimeout(() => res({
        1: { name: 'Floof', id: 1, bestFriend: 7 },
        7: { name: 'Rex', id: 7, bestFriend: 1 }
    }[id]), time)
})
%}
const twoPuppyNamesP =
    getPuppyById(1)                                   // first fetch
    .then(pup1 => getPuppyById(pup1.bestFriend)       // second fetch
        .then(friend => [pup1.name, friend.name])     // nested then
    )

twoPuppyNamesP  // our goal, a promise for the puppy and friend names
    .then(names => console.log('puppy names', names))
    .catch(e => console.error(e))
{% endrunkit %}

In such cases, it is **crucial to remember to `return`** the nested promise chain to the parent promise chain. In the example above we use the implicit return of an arrow function to accomplish this, but it is a common error to forget the `return` keyword when in a bracket-enclosed function body.

The biggest advantage that the above pattern has over an outer-scope variable is that it is stateless – there is no explicit mutation occurring in the visible code, only a declarative sequence of functional transformations.

As always, we can identify some disadvantages:

- this approach does not scale well for passing down each result from many `then` calls – one quickly returns to the "pyramid of doom" for such cases
- with nesting comes increased mental load in parsing and understanding the logic of the promise chain
- as is often the case with promise chains, it can be especially difficult to decide on a sensible formatting scheme with respect to where `.then` appears (same line? next line? indented?) and where to position the callback function

## Silly Experiment: Formatting Tricks

Speaking of formatting, there is no reason why one cannot format a nested promise chain in a "flat" way, if we allow for piling up of parentheses:

{% runkit
const getPuppyById = (id) => new Promise((res) => {
    const time = Math.random() * 500 + 500
    setTimeout(() => res({
        1: { name: 'Floof', id: 1, bestFriend: 7 },
        7: { name: 'Rex', id: 7, bestFriend: 1 }
    }[id]), time)
})
%}
const twoPuppyNamesP =
    getPuppyById(1)                              // first fetch
    .then(pup1 => getPuppyById(pup1.bestFriend)  // second fetch (missing closing paren)
    .then(friend => [pup1.name, friend.name]))   // nested then (extra closing paren)

twoPuppyNamesP  // our goal, a promise for the puppy and friend names
    .then(names => console.log('puppy names', names))
    .catch(e => console.error(e))
{% endrunkit %}

The longer the nested chain, the more we defer closing parens to the last line, where they will pile up like afterthoughts. In a language like [Haskell](http://haskell.org) in which function application doesn't _use_ parens, this isn't a problem! But for JavaScript, it gets a little silly. Compare and contrast:

```hs
-- Haskell

_then = (>>=) -- renaming for JS readers; can't use 'then' b/c it's a keyword

pupsIO =
    getPuppyById 1
    `_then` \pup1 -> getPuppyById (bestFriend pup1)
    `_then` \pup2 -> getPuppyById (bestFriend pup2)
    `_then` \pup3 -> getPuppyById (bestFriend pup3)
    `_then` \pup4 -> getPuppyById (bestFriend pup4)
    `_then` \pup5 -> pure [pup1, pup2, pup3, pup4, pup5]
```

```js
// JavaScript

const pupsP =
    getPuppyById(1)
    .then(pup1 => getPuppyById(pup1.bestFriend)
    .then(pup2 => getPuppyById(pup2.bestFriend)
    .then(pup3 => getPuppyById(pup3.bestFriend)
    .then(pup4 => getPuppyById(pup4.bestFriend)
    .then(pup5 => [pup1, pup2, pup3, pup4, pup5]))))) // lol
```

## The Promised Land: Async/Await

Moving past our promise chain woes, we return to the real issue at hand – promise chains are composed from callback functions, and functions syntactically introduce new scopes. If we didn't have sibling scopes, we could share access to previous results.

Lo and behold, this is one of the problems solved by `async` functions.

{% runkit
const getPuppyById = (id) => new Promise((res) => {
    const time = Math.random() * 500 + 500
    setTimeout(() => res({
        1: { name: 'Floof', id: 1, bestFriend: 7 },
        7: { name: 'Rex', id: 7, bestFriend: 1 }
    }[id]), time)
})
%}
const getTwoPuppyNamesP = async () => {  // a shared async function scope
    const pup1 = await getPuppyById(1)                 // first fetch
    const friend = await getPuppyById(pup1.bestFriend) // second fetch
    return [pup1.name, friend.name]                    // data transformation
}

const twoPuppyNamesP = getTwoPuppyNamesP() // async funcs return promises

twoPuppyNamesP  // our goal, a promise for the puppy and friend names
    .then(names => console.log('puppy names', names))
    .catch(e => console.error(e))
{% endrunkit %}

The advantages are substantial:

- far less noise (no `.then` calls or callback functions)
- synchronous-looking code with access to previous results in scope

The cost is pretty minimal:

- the `await` keyword may only be used inside an `async` function, so we need to wrap our promise code in a function body

Async/await is analogous to Haskell's `do`-notation, where `do` is like `async` and `<-` is like `await`:

```hs
-- Haskell

twoPuppyNames = do
    pup1   <- getPuppyById 1
    friend <- getPuppyById (bestFriend pup1)
    pure [name pup1, name friend]
```

One major difference is that `async`/`await` in JS is only for promises, whereas Haskell's `do` notation works with any [monad](https://wiki.haskell.org/Monad).

# Conclusion

With the advent of async/await, programmers are using raw promise chains less often. Async/await has its own subtleties to master, but it neatly solves at least one awkward aspect of promise chains, namely accessing previous async results in a sequence of operations.

As the title to this article suggested, when writing a manual promise chain it is sometimes perfectly valid to use a little local nesting. Doing so keeps multiple results in scope, without needing special library tricks or stateful assignments.

In any case, I hope that these examples will help people learning JS promises to understand them a little better and use them more confidently.