Promises in JavaScript
By: Rachel Andersen
Tags: JavaScript, Promises, Programming, Web Development, Coding

What is a Promise?

In JavaScript, as in life, a promise is something that you hope to receive in the future, but not now. In JavaScript, this “something” is a native object type with attached value(s). Promises deal with things happening in asynchronous time.

Real World Example

To understand promises, let’s first use a real world example. Let’s say your partner goes to the store, and, since you’ve been working hard all day, he or she promises to get you a nice bottle of wine. While your partner is at the store, the promise is unresolved/pending: you don’t know until he returns as to the state of the promise. From there, there are a total of three possible outcomes specifically concerning this promise: 1) your partner successfully returns with the wine and resolves/fulfils the promise, 2) your partner forgets the wine, resulting in the promise being rejected, and 3) your partner drives through a wormhole to another dimension and the wine promise is forever unresolved/pending. Similar to real world promises, JavaScript promises have 3 states: pending, resolved, and rejected.

Unresolved or Pending — A promise is pending when it’s not ready yet, perhaps waiting for a result value to be available.

— A promise is pending when it’s not ready yet, perhaps waiting for a result value to be available. Resolved or Fulfilled — A promise is resolved if the result value is available.

— A promise is resolved if the result value is available. Rejected — A promise is rejected if an error occurred.

In order to reach a resolved or rejected state, the promise must pass through a pending state.

States of a Promise in JavaScript

Promise Creation

A promise in JavaScript is created using a constructor function. The constructor function takes a callback function as a parameter, which in turn takes 2 parameters: resolve and reject, which in turn take one argument which is a value that can be a string, number, boolean, array, or an object. Resolve() and reject() are static method inherent to the promise, and therefore can also be invoked immediately upon the promise’s creation. Promises have other static methods:

all() — According to NearForm(2019), all “takes an array of Promises and returns a Promise that either fulfils when all of the Promises in the iterable argument have fulfilled or rejects as soon as one of the Promises rejects. If the returned Promise fulfils, it’s fulfilled with an array of the values from the fulfilled Promises in the same order as defined in the array argument. If the returned Promise rejects, it is rejected with the reason from the first Promise in the array that got rejected. This method can be useful for aggregating results of multiple Promises.”

allSettled()

race() — according to NearForm(2019), race(), “takes an array of Promises and executes them in the same way as Promise.all, the difference being it returns a Promise that fulfils or rejects as soon as one of the Promises in the iterable fulfils or rejects, with the value or reason from that Promise.” — useful for timeout functionality

Promise Constructor Function

Promise States

Promise Consumption

Now that we have been through promise creation, we need to talk about promise consumption. A promise is consumed by calling one of the following methods on the promise:

then()

catch()

finally()

The method then() takes 2 parameters: the first is a function that is called when the promise is resolved, while the second is a function that is called when the promise is rejected. In the event that you want to separate success from failure, you can use the catch() method to append a rejection handler to the promise. The method finally() can be used optionally, and usually performs general cleanup operations such as causing a loading indicator to stop running.

Promise Chaining

In order to use the methods mentioned above, they must be chained to the promise. Additional promises can also be chained to the original promise, as can be seen in the example below where the promise shareWine is chained to the original willIGetWine promise. Subsequently, another then method and a catch method are chained to handle the second promise’s fulfilment and rejection.

Chaining Promises & Promise Methods

Promises Uses

Chaining promises allows you to avoid the proverbial “callback hell” that is pervasive is more arcane ways of dealing with multiple asynchronous operations. Instead of the nightmare of nested callback functions seen below, promises could be used and each of these callbacks could be replaced with a .then() statement.

Callback Hell

Common Mistakes

Nested Promises — use all()

Broken Promise Chain

Mixing Sync & Async Code in a Promise Chain

Missing Catch

Forgetting to Return a Promise

Promisified Synchronized Code

Mixing Promise & Async/Await

Async Function that Returns a Promise

Defining a Callback as an Async Function

Conclusion

Promises are useful structures that not only allow you to avoid unnecessary complication in your code for asynchronous processes, but also make your code more legible.

A note from the Plain English team

Did you know that we have four publications and a YouTube channel? You can find all of this from our homepage at plainenglish.io — show some love by giving our publications a follow and subscribing to our YouTube channel!

References