How to escape Promise Hell

in #javascript8 years ago (edited)

Unlike Callback Hell, Promise Hell is accidentally self inflicted. Promise Hell is just a lack a familiarity with Promises.

This article will show some Promise Hell scenarios and the steps out get of it. All code snippets are written ECMAScript 2015.

Nested.then(..) with linearly dependent Promises

A typical example is Promises that depend on each other. Let’s say we have 3 Promises that depend on each other linearly: fetchBook(), formatBook(book), and sendBookToPrinter(book). The naïve code is:

fetchBook()
  .then((book) => {
    return formatBook(book)
      .then((book) => {
        return sendBookToPrinter(book);
      });
  });

The nesting is unnecessary, which simplies the code to:

fetchBook()
  .then((book) > {
    return formatBook(book);
  })
  .then((book) => {
    return sendBookToPrinter(book);
  });

Since these are single liners, the curly braces and return can be omitted.

fetchBook()
  .then((book) => formatBook(book))
  .then((book) => sendBookToPrinter(book));

But then we notice we have an identity closure! We can just inline the function:

fetchBook()
  .then(formatBook)
  .then(sendBookToPrinter);

Nested .then(..) with independent Promises

In another scenario, we might have Promises that can be run independently. Let’s say we have 3 functions that return Promises: demandMoreDonuts(), greet(name), and closeGate(). The naïve code would be:

demandMoreDonuts()
  .then(() => {
    return greet('fred')
      .then(() => {
        return closeGate();
      });
  });

How to get out of this situation depends if we care that these Promises are run in serial or in parallel. If we don’t care about the order of the Promises, then this can be simplified to:

Promise.all([
  demandMoreDonuts(),
  greet('fred'),
  closeGate()
]);

If we want to serialize the Promise order (say we want to abort early on error), then we can write this as:

demandMoreDonuts()
  .then(() => greet('fred'))
  .then(closeGate);

Nested.then(..) with multiple dependent Promises

This situation is what most people get hung up on with Promises. In this example, let’s say we have 4 Promises: connectDatabase(), findAllBooks(database), getCurrentUser(database), and pickTopRecommendation(books, user). The naïve code is:

connectDatabase()
  .then((database) => {
    return findAllBooks(database)
      .then((books) => {
        return getCurrentUser(database)
          .then((user) => {
            return pickTopRecommendation(books, user);
          });
      });
  });

Naïvely we will think this is the best we can do. How else can we get a reference to books and user at the same time? This code also has other issues, such as when we call getCurrentUser(database), we have books unnecessarily in scope. The solution is to understand Promises can be held in a reference. We can extract the common bits out.

const databasePromise = connectDatabase();

const booksPromise = databasePromise
  .then(findAllBooks);

const userPromise = databasePromise
  .then(getCurrentUser);

Promise.all([
  booksPromise,
  userPromise
])
  .then((values) => {
    const books = values[0];
    const user = valuess[1];
    return pickTopRecommentations(books, user);
  });

But wait, doesn’t this mean we connect to the database twice? We call .then(..) on databasePromise twice! This is a key thing to understand about Promises, they are only ever resolved once per creation. This means you can call .then(..) as many times as you want, and you’ll only ever get one database connection. This is not the case if you call connectDatabase() multiple times.

The extraction of the values out of promises is a bit ugly. It can be simplifed using the spread operator, but this is only available in Node.js 5.x or greater.

Promise.all([
  booksPromise,
  userPromise
])
  .then((values) => pickTopRecommentations(...values));

… or with destructuring, but this is only available in Node.js 6.x or greater.

Promise.all([
  booksPromise,
  userPromise
])
  .then(([books, user]) => pickTopRecommentations(books, user));

Alternatively it can be simplified with Ramda’s apply.

Promise.all([
  booksPromise,
  userPromise
])
  .then(R.apply(pickTopRecommentations));

Much more complex examples can be formed using a combination of these scenarios. I hope this will help somebody escape Promise Hell. If you have more specific examples that I have not covered, feel free to tweet me.

This article was originally posted on Medium and I am the original author (proof). @cheetah I have my eye on you.

Sort:  

how do you make greater than show up properly in markdown reformatted code? the site is auto converting to > to >

I did a bad thing. I used mimic and replaced > with >

Congratulations @pyrolistical! You have received a personal award!

2 Years on Steemit
Click on the badge to view your Board of Honor.

Do you like SteemitBoard's project? Then Vote for its witness and get one more award!

Congratulations @pyrolistical! You received a personal award!

Happy Birthday! - You are on the Steem blockchain for 3 years!

You can view your badges on your Steem Board and compare to others on the Steem Ranking

Vote for @Steemitboard as a witness to get one more award and increased upvotes!

Coin Marketplace

STEEM 0.20
TRX 0.14
JST 0.030
BTC 63718.61
ETH 3390.60
USDT 1.00
SBD 2.62