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,
Promise
, and what are its characteristics?Promise Chain
with examples.errors
in Promises?Mistakes
you may make in using Promises.interviews
?This article will cover the basic understanding of JavaScript promise and its characteristics, mainly in a beginner-friendly way.
If you like to learn from video content as well, this article is also available as a video tutorial here: π
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 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,
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 be rejected due to the disaster. Please note that 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 look new or confusing to you. We will revisit it at the end of this article.
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,COPY
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 the case of the Jack and Jill story, the executor
function may look like this,
resolve
:COPYlet promise = new Promise(function(resolve, reject) { // Got the water let value = 'water'; resolve(value); // An assurance of getting the water successfully });
reject
:COPYlet 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 });
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 water consumers 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 an error (rejected).
A promise object has the following internal properties,
pending
.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.
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 its possible outcomes(value
or error
).
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 rejected.
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
.COPY
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,COPY
promise.then(
(result) => {
console.log(result);
}
);
Similarly, if you are interested in only the error, pass null
as the value for the first argument.COPY
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, that you can do three very exceptional things inside the .then()
method,
return
another promise
from it.return
a value including undefined
.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.COPY
// 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,COPY
cooking rice with the Hurray! Fetched the Water.
So, three things happen in the above code,
.then()
handler for this purpose. Once they get the water, they start cooking. Note, here we define it, not calling it yet.This handler method can handle errors (rejections) from promises. As we already discussed, it is a much better syntax to handle the error situation than using the .then()
method. So let us now handle the “Jack fell down…” situation using JavaScript promise.COPY
// 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,
reject
method in the above code to reject the promise.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..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()
handler method performs cleanups like stopping a loader, closing a live connection, etc. The .finally()
method will be called whether a promise resolves or is rejected.COPY
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 in the promise chain article.
To Summarize,
Promise
is an important building block for the asynchronous concept in JavaScript.promise
object has two internal properties, state and result. These properties are not code-accessible..then()
, .catch()
, and .finally()
methods to handle promises.I hope now you can better relate to the analogy table.
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.
Source: GreenRoots Blog