JavaScript Promises - Explain Like I'm Five
Tapas Adhikary
Posted on August 5, 2021
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
On Twitter
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 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
});
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
});
- 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
});
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,
- 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.
- 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.
-
undefined: Initially, when the state value is
A promise that is either resolved or rejected is called settled.
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.
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);
}
);
If you are just interested in the successful outcome, you can chose to pass only one argument,
promise.then(
(result) => {
console.log(result);
}
);
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)
}
);
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
anotherpromise
from it. - You can
return
a value includingundefined
. - 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();
The Output,
cooking rice with the Hurray! Fetched the Water.
So, three things happen in the above code,
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.'
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.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();
The Output,
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 theresolve
method. However, it is recommended to use theError
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});
});
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,
Posted on August 5, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.