Photo by Andrew Petrov on Unsplash
[Promises 1/3] What is a promise? When to use it? How to construct one?
This is the 1st of a 3-part series of posts where we learn everything about promises
let a = 32;
a = a + 8;
console.log(a); // 40
The above code is pretty straightforward. You have a variable 'a' set to a number value 32. You intend to add 8 to it. And you want to see the value stored in the variable. It is never going to fail you.
But can you say the same for this?
let a = 32;
setTimeout(() => a = a + 8, 5000)
console.log(a); // 32
Why?
Because Javascript does not ALWAYS wait for a line of code to finish executing. Variable 'a' does take the value of 40 after 5000 ms and if you try console.log
after 5s, you may find the value you intend to see there. This is called non-blocking execution. Or what some people may refer to as asynchronous.
Javascript is special in the way that its engines treat every action that might make the code execution wait as "asynchronous" and it, therefore, does not wait for the task to finish. The other languages like python, C++, etc would rather wait for every function to finish executing and then move on to the next line. There are ways to make them execute in a parallel thread without waiting though but that is beyond the scope of this post.
This was a pretty simple example and not something that is very useful in the professional sense as we rarely wait for a command to execute on the basis of a fixed time!
Alright, so when do we use a promise?
Promises are useful when the JS engine needs to wait for tasks to finish and it does not know when they will be finished. Those tasks can either finish successfully or can result in some kind of failure.
I will broadly categorize promise use cases into these below:
- API Request => request a server and wait for a response
- File or media picker => wait for the user to choose a file or take image from camera before we can access it
- Web socket push => wait for a notification or message from a server
- Timeout => delay based waiting
What do promises look like? How to construct one?
let samplePromise = new Promise((res, rej) =>
{
if (condition)
// All the code for it work
res(value); // resolve
else
// All the code required to let the user know that something has failed
rej(); // reject
});
// Type 1
samplePromise
.then((value) => code)
.catch((error) => handle error)
.finally(() => code)
// Type 2
samplePromise
.catch((error) => handle error)
.then(() => code)
Creating a promise is creating an instance of the Promise class with its constructor accepting two methods/functions as arguments => resolve and reject. "Resolve" method is called when our condition for our said task is met. This can be any condition that you feel appropriate. Reject is used for cases when something intended fails. It is used for error handling so that our software does not crash. It proceeds while knowing that we have contingencies ready if such a failure arises.
Every promise instance can be followed by then, catch and finally.
.then, .catch, .finally
- .then => Executes when a promise resolves. It can also accept values that are passed as arguments to the resolve method of the promise instance. In our example above, we pass value and we receive it in Type 1.
- .catch => Executes when a promise rejects. It can also accept values that are passed as arguments to the reject method of the promise instance. We usually use this to catch errors and pass them as arguments to the promise's reject method so we can handle them.
- .finally => Code inside of .finally is called irrespective of the promise's outcome. We use it to remove a set flag or remove a delay or some other kind of reason where we always have to do something after our request action is complete.
In Type 1, if the promise resolves, we go from .then to .finally. .catch is skipped. if the promise rejects, we go from .catch to .finally. .then is skipped.
In Type 2, if the promise resolves, we go to .then. .catch is skipped. if the promise rejects, we go from .catch to .then. However, the value received in .then is the value that we return from within the catch block. If we return nothing, we can still use this code block to do things after all our processing is done. So, in a way, .then after a .catch is equivalent to a .finally.
Look at our first example once again. If we rewrite the function in the form of a promise, we will get our intended answer.
let a = 32;
let samplePromise = new Promise((res, rej) =>
{
setTimeout(() =>
{
a = a + 8;
res();
}, 5000)
});
samplePromise.then(() => console.log(a))
You will see that your code waits for 5s before showing up with the updated value of variable 'a'.
Another example
let a = 32;
let additionRequest = new Promise((res, rej) =>
{
if (isTodaySunday()) // conditon to check if today is sunday
{
a = a + 8;
res();
}
else
{
a = a - 8;
rej();
}
});
additionRequest
.then(() => console.log("The increased value of a is " + a)) // if fulfilled
.catch(() => console.log("The reduced value of a is " + a)) // if rejected
We setup the above code such that it adds 8 to 'a' only on Sundays but rejects your requests on other days. So, if your addition request is granted, the code inside your then block proceeds to execute. If your code is rejected, however, your catch block executes.
But what if I want to do something no matter the outcome?
Yes, that is what the finally block is for. Here is how the above example look like
let a = 32;
let additionRequest = new Promise((res, rej) =>
{
if (isTodaySunday()) // conditon to check if today is sunday
{
a = a + 8;
res();
}
else
{
a = a - 8;
rej();
}
});
additionRequest
.then(() => console.log("The increased value of a is " + a)) // if fulfilled
.catch(() => console.log("The reduced value of a is " + a)) // if rejected
.finally(() => console.log("Thank you for your service!")) // Will execute **after** either of the two
So, this is how you can write a promise that can take care of all outcomes and execute code only when the task attached to the promise is finished. Just a caution though, you need to know which functions inside of a promise block are actually synchronous. If there is another line of code within your promise block that needs to wait inside your promise block, then you need another promise for that as well. But thankfully, you don't need multiple blocks of .then, .catch nested one inside another and you can beautify them with something called Promise chaining which we will look into in the next chapter.
Thank You.