ES6 — This I Promise you

Maya Shavin
May 14 · 11 min read
Image for post
Image for post
A promise for a gift with candy — Inspired from Original screenshot

Promise is not only one of the most commonly used ES6 features but also a challenging concept to understand or explain, even for experienced developers. What exactly is Promise API, and why is it considered a significant improvement to JavaScript? We are going to find out in this post.

But first, we shall discuss two concepts — synchronous and asynchronous. What are they, and why are they related to Promise?

Let’s check it out, shall we? 😃


Synchronous and Asynchronous — What does it mean?

Thus before we start, let’s take a quick look at them.

Synchronous

Image for post
Image for post
Synchronous interactions at service counter queue

Asynchronous

Take the McDonald’s purchasing queue, for instance. Your turn ends once you made a purchase, but your purchase is still incomplete until you receive your food. And the process of getting your ready food doesn’t have to follow the same queue order or to interrupt any other on-going transaction at the purchasing counter.

Similarly, in programming, asynchronous means action can start, run in the background without disturbing other activities to proceed, and resume later if needed.

Image for post
Image for post
An example of how Asynchronous action executes in the same queues with other operations

So how are these two concepts related to JavaScript?

  • JavaScript engine executes one operation at a time.
  • If an operation is asynchronous — aka waiting for an external condition to be fulfilled (e.g., Ajax call), the engine does not stop and wait for it to finish. Instead, it continues to the next operation in the waiting queue and resumes that specific operation later if possible, once the queue is empty.

Great, so what is Promise? And how it relates to all of these?.

What is Promise?

In short, a Promise is a mechanism to allow asynchronous actions synchronously return values.

A promise is a result guarantee for a particular asynchronous operation on its execution. In other words, the related action makes a promise to return a final value, regardless of how long it takes to execute. And unlike the real world, it always keeps its promise 😄.

So how do we create a promise?

Creating a promise

const myPromise = new Promise(executor)

The executor itself receives two methods as its arguments — resolve and reject, and asynchronously executes target operation.

const executor = (resolve,reject) => { /* operation logic */ }
  • resolve() is called once the main logic of the operation completes the execution successfully (settles) — aka it happens when the created Promise resolves.
  • reject() is called when there is an error, or unaccepted result occurred — aka it happens when the created Promise should reject.

That’s the definition. It is entirely up to us to implement how we want to trigger resolve() and reject() in the executor’s logic. An example of common executor with resolve() and reject() is shown as below

Image for post
Image for post
Executor function implementation breakdown

Cool, now that we have a Promise created. What does it look like?

Image for post
Image for post
A created Promise in console

The returned output is the created Promise, in the form of an Object. So what does this object contain?

The Promise Object

  • The return value of the Promise after being settled ([[PromiseValue]])
  • Its status state ([[PromiseStatus]]).

Let’s look closely at the status state. There are three different states of a promise operation:

  • Fulfilled (or resolved in versions before Chrome Canary and Safari) ✅ — when the operation completed execution without error, meaning the Promise resolves (resolve() triggered).
  • Rejected 🔴 — as the opposite, when there is an error occurred during the operation, meaning when the Promise chooses to reject (reject() triggered).
  • Pending ⏳ — the operation is still in execution and not yet completed/failed.

Important note: when implementing the executor, you have to call resolve() or rejected() to indicate the status of the promise. Otherwise, the promise will always stay wrongly as pending, regardless of the actual execution result. As in the example below:

Image for post
Image for post
Promise is always in “pending” since no resolve() or reject() is called

Simple enough. What about the return value [[PromiseValue]]?

The return value of a promise is the value passed as an argument to the call resolve() or reject(), for instance:

Image for post
Image for post
The return value of Promise matches the data passed to resolve()

OK, we finally got a hang on the Promise object. Next question — what should we do once a Promise resolves/rejects?

How Promise works in chaining

  • .then() — is called after a Promise resolves (similar to .done() in Ajax).
  • .catch() — is called after a Promise rejects (similar to .fail() in Ajax)
  • .finally() — is always called at the end, regardless the status of a Promise. This method is similar to .always() in Ajax.

Each of the above accepts a callback function as an argument, and returns a new Promise, allowing chaining to another method if needed. While the callbacks of .then() and .catch() receives the return value from the chained Promise, the callback of .finally() is simply a method without argument.

Note here that .then() receives the same value passed to resolve() and similarly, .catch() receives the value passed to reject(). For example, we have a Promise that resolves when a random generated number is <5 and rejects otherwise:

const myPromise = new Promise((resolve, reject) => {
const randNum = generateRandom();
if (randNum < 5) {
resolve(`I'm resolved at ${randNum}`);
} else {
reject(`I'm rejected at ${randNum}`);
}
});

With a simple chaining as below

myPromise
.then(result => console.log(result))
.catch(error => console.log(error))
.finally(() => console.log("Completed"))

When myPromise resolves, the output will be:

Image for post
Image for post

And when it’s rejected:

Image for post
Image for post

The chaining flow is seen as follows:

Image for post
Image for post
Chaining flow of Promise’s built-in methods

Great. Now you know how Promise works in general, let’s do a little quiz to prove it, shall we 😉?

The Quiz

All the code is based on the example used in the section above.

  1. What is the output of the following code?
Image for post
Image for post

2. Now let’s change the order of chaining a bit, what will be the output?

Image for post
Image for post

I know 🤯. It’s not as simple as it may look like (or sound like). Whether you get all (or partial) correct or not, this is not easy, and even experienced developers don’t guess correctly either. But before we jump into the answers, let’s dive into Promise chaining a bit deeper.

Promise chaining breakdowns

In other words, if the callback executes successfully and returns a value, that value becomes the [[PromiseValue]] of the newly generated Promise. Its status [[PromiseStatus]] becomes fulfilled (or resolved) accordingly.

Otherwise, if there is any error (exception) that occurred (generally by using throw), the status will be set to rejected. And the new Promise will have the return value according to the error the throw action performs, as seen in the following example.

Image for post
Image for post

In short, a Promise chain can be broken down into a series of sub Promises, each with dedicated handlers and is executed following the order of chaining. Take our Quiz #1, for instance. It can be re-written as follows:

Image for post
Image for post

What happens if one of the sub Promises doesn’t have the proper handler for a specific state? For example, a handler .then() for the firstPromise when it is fulfilled (resolved), as shown in the code above? Or no .catch() for myPromise in case it’s rejected?

Well, in such a situation, the next Promise carries on what hasn’t been handled in the previous Promise. Then it either handles or proceeds that unhandled status to the following promise in the chain. And the process continues until there is a proper handler to execute.

This mechanism allows chaining to proceed safely until the last Promise handler in the chain without breaking.

So far, so good? By now, I hope the answers for the quiz become clearer 😉. Let’s check.

Quiz answers

Image for post
Image for post
Breaking down of Promise chaining into smaller promises

Hence, in case myPromise resolves, the output will look similar to

Image for post
Image for post

And in rejection it will be:

Image for post
Image for post

There is no throw operation triggered besides. Thus the second .catch() handler is never triggered.

2. The chaining can be broken down, similarly to #1 as below:

Image for post
Image for post

That implies, in case myPromise resolves, the output is similar to:

Image for post
Image for post

And in rejection:

Image for post
Image for post

Awesome. Since we (finally) understand Promise and its chaining mechanism, are there any other Promise APIs that can be useful to us?

Let’s find out.

Promise static methods

Promise.all()

  • All the Promises resolves — it will resolve with an array of values from resolved promises, in the same order as the input array. For example:
const promiseA = new Promise ((resolve, reject) => resolve('hello'))
const promiseB = new Promise ((resolve, reject) => resolve('world'))
Promise.all([promiseA, promiseB]).then(res => console.log(res))//["hello", "world"]
  • Any of the Promises get rejected — it will reject with the value of first rejected Promise in the input array. For example:
const promiseA = new Promise ((resolve, reject) => resolve('hello'))
const promiseB = new Promise ((resolve, reject) => reject('world'))
const promiseC = new Promise ((resolve, reject) => reject('2020'))
Promise.all([promiseA, promiseB, promiseC]).catch(err => console.log(err))//"world"

On the opposite, sometimes, we want to wait for at least one of the operations to complete and proceed. That’s when Promise.race() comes in.

Promise.race()

const promiseA = new Promise ((resolve, reject) => resolve('hello'))
const promiseB = new Promise ((resolve, reject) => resolve('world'))
Promise.race([promiseA, promiseB]).then(res => console.log(res))//"hello"

Or

const promiseA = new Promise ((resolve, reject) => reject('hello'))
const promiseB = new Promise ((resolve, reject) => resolve('world'))
const promiseC = new Promise ((resolve, reject) => reject('2020'))
Promise.race([promiseA, promiseB, promiseC]).catch(err => console.log(err))//"hello"

And last but not least, Promise.resolve() and Promise.reject()

Promise.resolve() and Promise.reject()

//Instead of
const promise = new Promise(res => res('Hello'))
//We can write as
const promise = Promise.resolve('Hello')
promise.then(res => console.log(res)) //'Hello'

Or similarly another example with Promise.reject()

//Instead of
const promise = new Promise((resolve, reject) => reject('Hello'))
//We can write as
const promise = Promise.reject('Hello')
promise.catch(res => console.log(res)) //'Hello'

Cool, isn’t it? And that’s the basics about Promise APIs we need to know, to start working with it in harmony 💁. What are the other things related to Promise we will discuss next?

What’s next?

Since a Promise is asynchronous, how JavaScript engine decides when to trigger callback handlers once that Promise resolves/rejects, among other synchronous operations in the same thread, is significant. In the next article, we will explore the following:

  • The order of execution in JavaScript engines,
  • How it schedules and choose a Promise’s callbacks in the Task Queues, and
  • How async/await (may) differs from Promise.

See you soon and enjoy reading! ❤️

Extra Resources

Promise documentation — MDN


If you like this post, don’t forget to give me a 👏 below ⏬️ . It will surely motivate me a lot 😊

If you love to read more from me, feel free to check out my articles or visit my portfolio website

If you’d like to catch up with me sometimes, follow me on Twitter | Facebook.

Frontend Weekly

It's really hard to keep up with all the front-end…

Maya Shavin

Written by

Senior Front-End Developer @Cloudinary www.mayashavin.com

Frontend Weekly

It's really hard to keep up with all the front-end development news out there. Let us help you. We hand-pick interesting articles related to front-end development. You can also subscribe to our weekly newsletter at http://frontendweekly.co

Maya Shavin

Written by

Senior Front-End Developer @Cloudinary www.mayashavin.com

Frontend Weekly

It's really hard to keep up with all the front-end development news out there. Let us help you. We hand-pick interesting articles related to front-end development. You can also subscribe to our weekly newsletter at http://frontendweekly.co

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store