Promises are one of the most exciting additions to JavaScript ES6. For supporting asynchronous programming, JavaScript uses callbacks, among other things. However, callbacks suffer from problems like Callback Hell/Pyramid of Doom. Promises are a pattern that greatly simplifies asynchronous programming by making the code look synchronous and avoid problems associated with callbacks.

In this article we are going to see what are promises, and how can we leverage them to our advantage.

What is a Promise?

The ECMA Committee defines a promise as —

A Promise is an object that is used as a placeholder for the eventual results of a deferred (and possibly asynchronous) computation.

Simply, a promise is a container for a future value. If you think for a moment, this is exactly how you use the word promise in your normal day-to-day conversation. For example, you book a flight ticket to go to India for travelling to the beautiful hill station Darjeeling. After booking, you get a ticket. That ticket is a promise by the airline that you will get a seat on the day of your departure. In essence, the ticket is a placeholder for a future value, namely, the seat.

Here’s another example — You promised your friend that you would return their book The Art of Computer Programming after reading. Here, your words act as the placeholder. The value is the said book.

You can think of other promise-like examples relating to various real-life situations like waiting at a doctor’s office, ordering food at a restaurant, issuing a book in a library, among others. All involve some form of a promise. However, examples only take us so far. Talk is cheap, so let’s see the code.

Making Promises

We create a promise when a certain task’s completion time is uncertain or too long. For example — A network request may take anywhere between 10ms to 200ms (or more) depending on the connection’s speed. We don’t want to wait while the data is being fetched. 200ms may seem less to you but it’s a (very) long time for a computer. Promises are all about making this type of asynchrony easy and effortless. Let’s get to the basics.

A new promise is created by the using the Promise constructor. Like this —

Promise Example

Observe that the constructor accepts a function with two parameters. This function is called an executor function and it describes the computation to be done. The parameters conventionally named resolve and reject, mark successful and unsuccessful eventual completion of the executor function, respectively.

The resolve and reject are functions themselves and are used to send back values to the promise object. When the computation is successful or the future value is ready, we send the value back using the resolve function. We say that the promise has been resolved.

If the computation fails or encounters an error, we signal that by passing the error object in the reject function. We say that the promise has been rejected. reject accepts any value. However, it is recommended to pass an Error object since it helps in debugging by viewing the stacktrace.

In the above example, Math.random() is used to generate a random number. In 90% of the cases, the promise will be resolved (assuming equal probability distribution). It will be rejected in the rest of the cases.

Using Promises

In the above example, we created a promise and stored it in myPromise . How can we access the the value passed by the resolve or reject function?All Promise instances have a .then() method on them. Let’s see —

Using Promises

.then() accepts two callbacks. The first callback is invoked when the promise is resolved. The second callback is executed when the promise is rejected.

Two functions are defined on line 10 and 11, onResolved and onRejected . They are passed as callbacks to the .then() on line 13. You can also use the more idiomatic style of writing a .then as done in line 16 to 20. It offers the same functionality as the above .then .

A few important things to note in the previous example.

We created a promise myPromise . We attached a .then handler two times: on line 13, and 16. Though, they are same in functionality, they are treated as different handlers. However —

A promise can only succeed(resolved) or fail(reject) once. It cannot succeed or fail twice, neither can it switch from success to failure or vice versa.

If a promise has succeeded or failed and you later add a success/failure callback (i.e a .then ), the correct callback will be called, even though the event took place earlier.

That means once the promise reaches a final state, the state won’t change (that is, the computation will not be done again ) even if you attach .then handler multiple times.

To verify this, you can see a console.log statement on line 3. When you run the above code with both .then handler, the logged statement will be printed only once. It shows that the promise caches the result, and will give the same result next time.

The other important thing to note is that a promise is evaluated eagerly. Itstarts its execution as soon as you declare and bind it to a variables. There is no .start or .begin method. Like it began in the previous example.

To ensure that promises are not fired immediately but evaluates lazily, we wrap them in functions. We’ll see an example of this later.

Catching Promises

Till now we conveniently saw only the resolve cases. What happens when an error occurs in the executor function. When an error occurs, the second callback of .then() , that is, onRejected is executed. Let’s see an example —

Errors in Promises

It’s the same as first example, but now it rejects with 90 percent probability and throws an error in 10% of the cases.

On line 10 and 11 we have defined onResolved and onRejected callbacks , respectively. Note that onRejected will be executed even if an error was thrown. It’s not necessary to reject a promise by passing an error in the reject function. That is, a promise is reject in both cases.

Since error handling is a necessity for robust programs, a shortcut is given for such a case. Instead of writing .then(null, () => {...}) when we want to handle an error, we can use .catch(onRejected) which accepts one callback: onRejected . Here’s how the above code will look with a catch handler —

myProimse.catch(

(error) => console.log(error.message)

);

Remember that .catch is just a syntactical sugar for .then(undefined, onRejected) .

Chaining Promises

.then() and . catch() methods always return a promise. So you can chain multiple .then calls together. Let’s understand it by an example.

First, we create a delay function that returns a promise. The returned promise will resolve after the given number of seconds. Here’s its implementation —

const delay = (ms) => new Promise(

(resolve) => setTimeout(resolve, ms)

);

In this example, we are using a function to wrap our promise so that it does not execute immediately. The delay function accepts the time in milliseconds as a parameter. The executor function has access to the ms parameter due to closure. It also contains a setTimeout that calls the resolve function after ms milliseconds pass, effectively resolving the promise. Here’s an example usage —

delay(5000).then(() => console.log('Resolved after 5 seconds'));

The statements in the .then callback will run only after delay(5000) resolves. When you run the above code, you’ll see Resolved after 5 seconds printed five seconds later.

Here’s how we can chain multiple .then() calls —

Chaining multiple Promises

We begin at line 5. The steps undertaken are —

The delay(2000) function returns a promise that gets resolved after two seconds.

function returns a promise that gets resolved after two seconds. The first .then() executes. It logs a sentence Resolved after 2 seconds . Then, it return another promise by calling delay(1500) . If a .then() returns a promise, the resolution (technically called settlement) of the that promise is forwarded to next .then call.

executes. It logs a sentence . Then, it return another promise by calling . If a returns a promise, the resolution (technically called settlement) of the that promise is forwarded to next call. This continues as long as the chain is.

Also note line 15. We are throwing an error in the .then . That means the current promise is rejected, and is caught in the next .catch handler.Hence, Caught an error gets printed. However, a .catch itself is alwaysresolved as a promise, and not rejected (unless you intentionally throw an error). That’s why the .then following .catch is executed.

It is recommended to use .catch and not .then with both onResolved and onRejected parameters. Here’s a case explaining why —

Line 1 creates a promise that always resolves. When you have a .then with two callbacks, onResolved and onRejected , you can only handle errors and rejections of the executor function. Suppose that the handler in .then also throws an error. It won’t lead to the execution of onRejected callback as shown on lines 6–9.