What is JavaScript all about: Promise

Maayan Savir
5 min readAug 12, 2020
Photo by boris misevic on Unsplash

JavaScript runs code sequentially in top-down order. Sometimes (which actually happen a lot) we want to run code after function has finish executed or an event has happened means to run the code in an asynchronous way.
Since JavaScript is synchronized language, we need to find a way to run it in an asynchronous way.
To achieve it we use callback functions. a callback function gives us the ability to execute a function/event/action in an asynchronous way.

But wait there is a problem
While callback functions are the way to handle the synchronous problem, callback functions have some downsides such as the pyramid of doom (or the callback hell).

Promise to the rescue

Promises didn’t go mainstream right away. Actually, when the community start to look for ways to handle asynchronous behavior, callback functions were chosen. It is only when jQuery started to implement Promise on their code base (which become popular and widely used) when Promise started to get reputation.

Early implementations of promises began to appear as early as the 1980’s. The use of the word “promise” was coined by Barbara Liskov and Liuba Shrira in 1988.

A promise is an object that may produce a single value some time in the future. It has 3 different states:

  • Fulfilled:resolve()
  • Rejected: reject()
  • Pending: not yet fulfilled or rejected — A pending promise can either be fulfilled with a value, or rejected with a reason (error).

Promise constructor

The function passed to new Promise is called the executor. Its arguments resolve and reject are callbacks provided by JavaScript itself. Our code is only inside the executor.
the executor runs automatically and attempts to perform a job. When it is finished with the attempt it calls resolve if it was successful or reject if there was an error.

The promise object returned by the new Promise constructor has these internal properties:

  • state — initially "pending", then changes to either "fulfilled" when resolve is called or "rejected" when reject is called.
  • result — initially undefined, then changes to value when resolve(value) called or error when reject(error) is called.

Successful job — fulfilled promise

When calling the Promise constructor with new Promise() The executor is called automatically and immediately and receives two arguments resolve and reject . Both of them are pre-defined by the JavaScript engine, so we don’t need to create them. We should only call one of them when ready.

After one second of “processing” the executor calls resolve("done") to produce the result. This changes the state of the promise object:

Unsuccessful job — rejecting the promise

This time, the job had an error and therefore we rejected the promise and move the promise object to "rejected" state:

the executor should perform a job (usually something that takes time) and then call resolve or reject to change the state of the corresponding promise object

The properties state and result of the Promise object are internal. We can’t directly access them. We can use the methods .then/.catch/.finally for that.

then, catch, finally

A Promise object serves as a link between the executor and the consuming functions, which will receive the result or error. Consuming functions can be registered (subscribed) using methods .then, .catch and .finally.

then

then() takes two arguments, a callback for a success case, and another for the failure case. having both functions are optional, so we can add a callback for the success or failure case only.

The first callback is invoked when the promise is resolved. The second callback is executed when the promise is rejected.

With the above example, we generate a random number and resolve/reject the promise depends if the number is grater or smaller than 8 (to get an error every time, we can change the number to 0).
Then, with the next example, we log to the console the result if the promise was resolved or rejected.

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.

If we’re interested only in successful completions, then we can provide only one function argument to .then:

catch

If we’re interested only in errors, then we can use null as the first argument: .then(null, errorHandlingFunction). Or we can use .catch(errorHandlingFunction), which is exactly the same:

finally

Finally also accept a function handler.
finally is a good handler for performing cleanup or to perform any action regardless if the promise has been resolved or rejected.

The call .finally(f) is similar to .then(f, f) in the sense that f always runs when the promise is settled: be it resolve or reject but with some differences.

  1. A finally handler has no arguments. In finally we don’t know whether the promise is successful or not. That’s all right, as our task is usually to perform “general” finalizing procedures.
  2. A finally handler passes through results and errors to the next handler.

In the above example, the promise will get resolved and eventually we will see on the console “all good!” (after 1 second). But before that, we use finally and this will be called before the resolved handler method.
It will act the same if the promise was rejected.

Chaining

Did you see how in the last example I chained .then to .finally . This is another advantage for thePromise object and this is when promises really start to stand out from simple callback patterns.

The idea is that the result is passed through the chain of .then handlers.
When a handler returns a value, it becomes the result of that promise, so the next .then is called with it.

The whole chaining works, because a call to promise.then returns a promise, so that we can call the next .then on it.

Keep in mind that technically we can also add many .then to a single promise. But this is not chaining.

What we did here is just several handlers to one promise. They don’t pass the result to each other; instead they process it independently.

The same as we chaining bunch of .then() we can use .catch() to catch error during the chain.

Let’s take a look at a little bit more complex example uses chaining with .then() and .catch()

we have created 3 different functions where each of them returns a Promise .
Now, we can call them like that

If, in any point of the chain, the promise would be rejected, we catch the error and print it and as we already learned, finally will be printed no matter what the promise status would be.

--

--