CodeNewbie Community

Cover image for JavaScript Promises - Explain Like I'm Five
Tapas Adhikary
Tapas Adhikary

Posted on • Originally published at blog.greenroots.info

JavaScript Promises - Explain Like I'm Five

If you found this article helpful, you will most likely find my tweets useful too. So make sure you follow me on Twitter for more information about web development and content creation. This article was originally published on my Blog.


Hello friends ๐Ÿ‘‹, welcome to the first article of my brand new series, Demystifying JavaScript Promises - A New Way to Learn. JavaScript promises are very special. As web developers, we hardly have a way to avoid learning about it. Believe me, if not you; your interviewers indeed love promises ๐Ÿ˜‰!

On the other hand, If we take a poll on the "Hardest JavaScript concept someone is dealing with?" you would see 'promise' is making its way towards the top of the list. Don't you believe me? Here is a recent poll result ๐Ÿ™‚.

On LinkedIn

LinkedIn Poll.png

On Twitter

Twitter Poll.png

Hence it certainly makes promises 'the topic' of discussion. In this series, you will learn about JavaScript Promises from beginners to advanced levels. We will cover,

  • What is Promise and its characteristics?
  • Promise Chain with examples.
  • How to deal with errors in Promises?
  • Mistakes you may make in using Promises.
  • How to prepare for your (promise) interviews?

This article will cover the basic understanding of JavaScript promise and its characteristics, mainly in a beginner-friendly way.

The Jack and Jill Story

The "Jack and Jill Went Up the Hill..." rhyme has two primary characters, Jack the small boy and his sister Jill. Let's twist the story. Let's introduce their grandparents.

So, Jack & Jill promise their grandparents to fetch some water from the well at the top of the hill. They started on their mission to get it. In the meantime, the grandparents are busy discussing the daily routine, and they want to start cooking once the kids are back with the water.

The Jack and Jill Story
๐Ÿ’ก The illustration above is my improvisation on the famous Jack and Jill rhyme. Any similarities of it with anything in this world are purely coincidental. ๐Ÿ™‚

Now there are two possibilities,

  • Jack and Jill come down with the water, and the cooking starts.
  • "Jack fell down and broke his crown. And Jill came tumbling after." - In this case, Jack and Jill return, but unfortunately, they do not get the water.

In this short story, there is a promise of getting the water using the activity of fetching it. The promise can get fulfilled(getting the water) by the kids or reject due to the disaster. Please note, while Jack and Jill were working on executing the promise, the grandparents were not sitting idle. They were planning the day.

The JavaScript promises also work similarly. As developers, we create them to fetch something(data from a data store, configurations, and many more). Usually, the fetching may not happen instantly. We want to fetch things asynchronously. It means we do not want the application to wait for the response, but we can continue to work on the response when it is available.

Hence our analogy table may look like this,

In Real Life(with JavaScript) In Our Story
Promise Water fetching by Jack ๐Ÿ‘ฆ and Jill ๐Ÿ‘ง
Executor Function Fetch the Water ๐Ÿƒโ€โ™€๏ธ ๐Ÿƒโ€โ™‚๏ธ
Activity Fetch ๐Ÿงถ
Expected data in response Water ๐Ÿ’ง
Consumers Grandparents ๐Ÿ‘ต ๐Ÿ‘ด
resolve/fulfilled โœ”๏ธ Successfully get the water for cooking
reject/rejected โŒ Disaster(error) in getting the water
Task after getting the data successfully Cooking ๐Ÿš

Don't worry if some of the terms looking new or confusing to you. We will revisit it at the end of this article.

Promise in JavaScript

A promise is a JavaScript object that allows you to make asynchronous(aka async) calls. It produces a value when the async operation completes successfully or produces an error if it doesn't complete.

You can create promise using the constructor method,

let promise = new Promise(function(resolve, reject) {    
    // Do something and either resolve or reject
});
Enter fullscreen mode Exit fullscreen mode

We need to pass a function to the Promise Constructor. That function is called the executor function(Remember, fetching the water?). The executor function takes two arguments, resolve and reject. These two are callback functions for the executor to announce an outcome.

The resolve method indicates successful completion of the task(fetching water), and the reject method indicates an error(the disaster). You do not implement the resolve/reject method. JavaScript provides it to you. You need to call them from the executor function.

So, in case of the Jack and Jill story, the executor function may look like,

  • Example of the resolve:
 let promise = new Promise(function(resolve, reject) {
      // Got the water
      let value = 'water';
      resolve(value); // An assurance of getting the water successfully
 });
Enter fullscreen mode Exit fullscreen mode
  • Example of the reject:
 let promise = new Promise(function(resolve, reject) {
      // OOPS, Jack fell down and broke his crown. 
      // And Jill came tumbling after.
      reject(new Error("Disaster")); // Throwing and error
 });
Enter fullscreen mode Exit fullscreen mode

The Promise object and States

In the Jack and Jill story, the grandparents were not waiting for the kids to fetch the water. They were planning the day in the meantime. But Jack and Jill informed them in both the cases of getting the water successfully or meeting with the disaster. Also, the grandparents were the consumers of the water to cook the food.

Similarly, the promise object should be capable of informing the consumers when the execution has been started, completed (resolved), or returned with error (rejected).

A promise object has the following internal properties,

  1. state: This property can have the following values,
    • pending: When the execution function starts. In our story, when Jack and Jill start to fetch the water.
    • fulfilled: When the promise resolves successfully. Like, Jack and Jill are back with the water.
    • rejected: When the promise rejects. Example. Jack and Jill couldn't complete the mission.
  2. result: This property can have the following values,
    • undefined: Initially, when the state value is pending.
    • value: When the promise is resolved(value).
    • error: When the promise is rejected.

A promise that is either resolved or rejected is called settled.

states_2.png

So the consumers(like the grandparents) need to rely on the promise object to know the state and value/error.

Handling Promises by the Consumers

The promise object returned by the new Promise constructor has it all. A consumer can use it to know the state(pending, fulfilled, or rejected) and the possible outcomes(value or error) from it.

But hold on. These properties are internal. They are code-inaccessible, but they are inspectable. It means that we will be able to inspect the state and result property values using a debugger tool, but we will not be able to access them directly using the program.

So then? That's where we have three important handler methods, .then(), .catch(), and .finally(). These methods help us create a link between the executor and the consumer when a promise resolves or rejects.

executor-consumer.png

The .then() Promise Handler

We get a .then() method from every promise. The sole purpose of this method is to let the consumer know about the outcome of a promise. It accepts two functions as arguments, result and error.

promise.then(
  (result) => { 
     console.log(result);
  },
  (error) => { 
     console.log(error);
  }
);
Enter fullscreen mode Exit fullscreen mode

If you are just interested in the successful outcome, you can chose to pass only one argument,

promise.then(
  (result) => { 
      console.log(result);
  }
);
Enter fullscreen mode Exit fullscreen mode

Similarly, if you are interested in only the error, pass null as the value for the first argument.

promise.then(
  null,
  (error) => { 
      console.log(error)
  }
);
Enter fullscreen mode Exit fullscreen mode

It is a bit odd syntax to pass a null explicitly for an error case. That's where we have an alternative called the .catch() method we will see soon.

Also note, you can do three very exceptional things inside the .then() method,

  • You can return another promise from it.
  • You can return a value including undefined.
  • You can throw an error.

These three points will be the basis of learning the Promise Chain in the future article. Now, let's write the code for Jack and Jill, fulfilling the promise of getting water to their grandparents.

// 1. Create a Promise to fetch the water
let promise = new Promise(function(resolve, reject) {
 // Pretend a delay of 2 sec to fetch it!
  setTimeout(function() {
      // Fetched the water. Let's resolve the promise
      resolve('Hurray! Fetched the Water.');
  }, 2000);
});

// 2. Function to Set up the handler to handle a promise result.
// It is to inform the grandparents when the result is available.
const grandParentsCooking = () => {
  // The handler function to handle the resolved promise
  promise.then(function(result) {
    // Fetched the water. Now grandparents can start the cooking
    console.log(`cooking rice with the ${result}`);
  });
}

// 3. Calling the function to activate the set up.
grandParentsCooking();
Enter fullscreen mode Exit fullscreen mode

The Output,

cooking rice with the Hurray! Fetched the Water.
Enter fullscreen mode Exit fullscreen mode

So, three things happen in the above code,

  1. We create the promise. In the executor function, we delay 2 seconds to pretend an async call(actually, climbing hills and fetching water takes a lot more!). Then we resolve the promise saying, 'Hurray! Fetched the Water.'

  2. We have set up an information mechanism for the grandparents to know when the water is fetched successfully. We use the .then() handler for this purpose. Once they get the water, they start cooking. Note, here we define it, not calling it yet.

  3. Activating the handler by calling the function.

The .catch() Promise Handler

You can use this handler method to handle errors (rejections) from promises. As we discussed already, it is a much better syntax to handle the error situation than handling it using the .then() method. So let us now handle the "Jack fell down..." situation using JavaScript promise.

// 1. Create the promise
let promise = new Promise(function(resolve, reject) {
  setTimeout(function() {
      // Reject it as the disaster happend.
      reject(new Error('Jack fell down and broke his crown. And Jill came tumbling after.'));
  }, 2000);
});

// 2. Inform grandparents 
// but this time we are using the .catch
const grandParentsCooking = () => {
  promise.catch(function(error) {
    console.error(`OMG ${error.message}`);
  });
}

// 3. Call the function
grandParentsCooking();
Enter fullscreen mode Exit fullscreen mode

The Output,

image.png

A few points to note,

  • We use the reject method in the above code to reject the promise.
  • You can pass any type of argument to the reject method like the resolve method. However, it is recommended to use the Error objects. We will discuss it in detail in the future article on error handling with promise.
  • We use the .catch() handler to handle the rejection. In the real world, you will have both .then() and .catch() methods to handle the resolve and reject scenarios. We will learn it in the promise chaining article of the series.

The .finally() Promise Handler

The .finally() handler method performs cleanups like stopping a loader, closing a live connection, and so on. Irrespective of whether a promise resolves or rejects, the .finally() method will be called.

let loading = true;
loading && console.log('Loading...');

// Getting the promise
promise = getPromise();

promise.finally(() => {
    loading = false;
    console.log(`Promise Settled and loading is ${loading}`);
}).then((result) => {
    console.log({result});
});
Enter fullscreen mode Exit fullscreen mode

The vital point to note, the .finally() method passes through the result or error to the next handler, which can call a .then() or .catch() again. It is convenient, and we will see many examples of it in the promise chain article.

In Summary

To Summarize,

  • Promise is an important building block for the asynchronous concept in JavaScript.
  • You can create a promise using the constructor function.
  • The constructor accepts an executor function as an argument and returns a promise object.
  • A promise object has two internal properties, state and result. These properties are not code-accessible.
  • The consumer of a promise can use the .then(), .catch(), and .finally() methods to handle promises.
  • The Promise is better understood using examples, like the Jack and Jill Story.

I hope now you will be able to relate to the analogy table better.

In Real Life(with JavaScript) In Our Story
Promise Water fetching by Jack ๐Ÿ‘ฆ and Jill ๐Ÿ‘ง
Executor Function Fetch the Water ๐Ÿƒโ€โ™€๏ธ ๐Ÿƒโ€โ™‚๏ธ
Activity Fetch ๐Ÿงถ
Expected data in response Water ๐Ÿ’ง
Consumers Grandparents ๐Ÿ‘ต ๐Ÿ‘ด
resolve/fulfilled โœ”๏ธ Successfully get the water for cooking
reject/rejected โŒ Disaster(error) in getting the water
Task after getting the data successfully Cooking ๐Ÿš

That's all for now. Please stay tuned for the second article of the series. We will learn about the Promise Chain with another story.



I hope you enjoyed this article or found it helpful. Let's connect. Please find me on Twitter(@tapasadhikary), sharing thoughts, tips, and code practices.

You may also like,

Discussion (0)