- You are waiting for me to come (the promise is in a PENDING state)
- I bought you a bar of chocolate (the promise is in a FULFILLED state)
- The shop was closed, or due to any reason, the promise failed ( the promise goes to the REJECTED state).
Similarly, when you call an asynchronous function, i.e., a function that might send a response in the future, you are actually taking a promise. At first, the promise remains in the PENDING state because no response has been received. After some time, when the response is received, there are two possible cases:
- We got a response (i.e., the promise goes to the FULFILLED state)
- We got an error and no response (the promise got REJECTED)
Note: Getting a response means the promise is in the SETTLED state. In other words, the promise is not in the PENDING state now, and it has been either RESOLVED or REJECTED.
- To handle blocking of execution thread because asynchronous tasks take time to complete.
This is why we need ways to handle asynchronous tasks carefully. We need a way in which these time-consuming tasks do not block the thread, and other tasks can be carried out meanwhile the asynchronous tasks get their response. We need a way to get notified when the asynchronous task gets resolved so that we can do something related to that task after it has been completed.
Callback Vs Promises for Asynchronous Operations
Let us look at both in a little more detail:
- Code written with promises is easy to read and maintain.
- Syntax is not easy to read and understand.
- Error handling is not so easy with callbacks.
How Promises Work
Let us understand this in a bit more detail. The moment a promise is invoked, it goes to the PENDINGstate and waits for aresponseto come. We might get the correct response, which makes the promise FULFILLED, or we might get an error, i.e., the promise goes to the REJECTED state. Apromiseis said to beSETTLEDif it is not in thepending state`, i.e., either the response has arrived or an error has occurred.
Note: Once settled, a promise cannot be resettled.
The Important Rules of Promise
- A standard promise comes with a pre-defined .then() method.
- A promise is initially in the pending state, which transitions into a fulfilled or rejected state after some time.
- Fulfilled or rejected states are settled states. A promise, once settled, cannot be resettled, i.e., a settled state can not transition into any other state.
- Once settled, a promise will have value. If fulfilled, the promise will have the data requested. If rejected, it will contain undefined. But it will always have a value after being settled, which cannot be changed.
.then() always returns another promise. So, we can chain promises like this.
Promise Chaining works like try, and catch, but it makes asynchronous code look more like the synchronous one. The code here works sequentially, i.e., line by line.
Incumbent settings object tracking
- Create your own promise using the Promise() constructor.
- Pass the executor function as the parameter to the constructor.
- The executor function gets executed whenever the promise is created.
Executor Function is passed as a parameter to the Promise constructor. It is executed by the constructor function whenever a new promise is created using the constructor.
We can create promises using the Promise constructor in the following way:
- Promise.all() resolves when all the promises resolve.
- Promise.allSettled() resolves when all the promises settle, i.e., either fulfill or get rejected.
- Promise.any() resolves as soon as any one of the promises resolves.
- Promise.race() resolves as soon as any one of the promises settles, i.e., either fulfills or gets rejected.
- promise.reject(reason) return a new rejected promise with the reason for rejection some as passed.
- promise.resolve(value) return a new resolved promise with the value some as passed.
Promise.all(iterable) This method takes an iterable of promises and resolves to a single promise that resolves when either all the promises in the iterable resolves or any one rejects.
Case I: Resolves when all the promises resolve: In this case, the final resolved value will be an array containing aggregated values from all the promises from the iterable.
Case II: Resolves when any one of the rejects: In this case, the reason for the rejected promise is the reason the first promise got rejected from the iterable.
Promise.allSettled(iterable) This method takes an iterable of promises and returns a new promise only when all the promises get settled, i.e., are in fulfilled or rejected state. This method is mainly used when the promises passed in the iterable are not connected to each other, and we just want to know what they result in.
On the other hand, Promise.all() is used when the promises are interdependent, and we want to stop the execution when any one of them gets rejected.
- Promise.any(iterable) This method takes an iterable of promises, and as soon as one of the promises gets resolved, it gets resolved with the value of the first resolved promise.
If none of the promises gets resolved, i.e., all of them get rejected, then the promise gets rejected with an aggregated error formed by grouping all the individual errors.
- Promise.race(iterable) This method takes an iterable of promises and returns a promise that fulfills or rejects as soon as any one of the promises from the iterable gets fulfilled or rejected.
If fulfilled, the final returned promise has the value of the fulfilled promise. If rejected, it picks the reason from the rejected promise.
- Promise.reject(reason) This method is used to return a new rejected promise with the reason same as the one passed as the parameter.
- Promise.resolve(value) This method returns a resolved promise with the value passed as the parameter. If the value is thenable (i.e., then can be used ), it will follow the then's and then return the final value.
Note: In cases where we do not know if a value is a promise, promise.resolve() it, and it will return the value as a promise.
- Promise.prototype.then() handles the value for the promise in case it is fulfilled successfully.
- Promise.prototype.catch() handles the promise in case an error occurs or the promise gets rejected.
- Promise.prototype.finally() runs a piece of code as soon as the promise settles, regardless of the promise's status - fulfilled or rejected.
- Promise.prototype.then() This method takes in two parameters - callback functions for success and failure (both optional) and returns a promise.
Syntax: p.then(onFulfilled[, onRejected]); Parameters
- onFulfilled This function is called if the promise fulfills.
- onRejected This function is called if the promise rejects.
Note: Both these functions are optional.
- Promise.Prototype.catch() This method handles errors for the promise. If an error occurs, the catch method executes the error handler function passed as a parameter to the catch method.
Syntax: p.catch(onRejected); Parameter: onRejected is the function to be called when an error occurs or the promise gets rejected.
Note: In the previous section, we handled the error using the second argument to the then() method, and in this section, we're handling the same using the catch() method. Which one is the right way to do it?
Both of them are internally the same. The catch() method internally uses then(onFulfilled, onRejected) to handle the rejected promise! So, a promise can be set to handle errors/rejections both ways.
- Promise.Prototype.finally() This method returns a new promise when the promise finally goes to the settled state, i.e., it gets fulfilled or gets rejected. This is a handy method when we want to run a piece of code when the promise settles, no matter if it gets rejected or fulfilled.
Syntax: p.finally(onFinally); Parameter: onFinally is the function that is executed when the promise either fullfills or gets rejected.
How to Cancel a Promise?
Note: Do not use .cancel() because it violates the standard rules for creating promises because the function creating the promise should only be allowed to reject or fulfill or cancel that.
- A promise is an object that contains the results of an asynchronous function.
So, how do we extract the result from that object once it gets fulfilled successfully? Or how do we get to know that the promise got rejected or if it faced an error? And at last, how do we handle errors/rejections here?
Extracting the Promise Fulfilled value
The then() method (as discussed earlier in the Instance Methods Section) is used to extract the result from the async task.
Extracting the Promise Rejection Error
.catch() method is used to extract out the reason for rejection (if the promise rejects).
Extracting Value and Error
Since both .then() and .catch() return a new promise, they can be chained. And both the cases - promise getting fulfilled or getting rejected can be handled using this syntax:
The Native JS Promise Extras
The native Promise object offers additional functionalities that may capture your interest. These features include:
Promise.reject(): This function returns a promise that is rejected, allowing you to handle errors or exceptional cases in your code. Promise.resolve(): Conversely, Promise.resolve() returns a promise that is resolved, enabling you to handle successful outcomes or values in your code. Promise.race(): By providing an array or any iterable, Promise.race() returns a promise that resolves with the value of the first promise that is resolved within the iterable. If any promise within the iterable rejects, the returned promise will be rejected with the corresponding reason. Promise.all(): Taking an array or any iterable as input, Promise.all() returns a promise that resolves only when all the promises within the iterable have resolved successfully. However, if any of the promises reject, the returned promise will be rejected with the reason of the first rejected promise encountered.
Benefits of Promises
- Promises are better than Callbacks as callbacks create a situation called callback hell
- Code is readable and better maintained when promises are used to handle async code than when callbacks are used.
- Synchronous tasks
- Asynchronous tasks
We want to getData and then console it. Since getData is an asynchronous task, i.e., it will return the data immediately, this piece of code works perfectly fine.
When it comes to asynchronous tasks, i.e., where we might not get a response immediately, we need to handle this in a very different way. One of the ways is to use callbacks. Consider this:
We are sending the action to be performed after the completion of the asynchronous task as a parameter to the first function. Suppose we want to filter the data and display an image corresponding to this id.
Note: Loading an image from the database will also need time and is an asynchronous task.
Now, this can lead to callback hell,
Look at this, the same code with callback being written with promises.
The flow of code here is much easier to understand and readable. Hence, some of the evident benefits of using promises are:
- Helps us handle asynchronous tasks in a much more efficient way
- Helps improve the code quality and readability.
- Better error handling using catch().
- Avoids callback hell and improves the flow of control definition
Look at a few examples to understand promises in detail:
Explanation: A promise is created using the Promise() constructor in the code above. This promise will resolve or reject based on the value of the count variable.
- On consoling out promiseCount, we get a promise as the output. But how do we extract the response received? And what about the case our promise rejects because of the negative value of the count variable?
- We can use .then() to extract out the value of the fulfilled promise and .catch() to extract out the reason for rejection (in case it gets rejected) in the following way:
Now let us look at a more realistic example. In every web application, we need to send requests to the server for data to be displayed to the view (on the browser).
This request takes some to come back with the requested data. At times, the request might encounter an error while processing. But our web app cannot wait for such long. Hence, we use promises to handle this task.
We'll use the fetch() method to make a request to the server and get a random fact about cats. fetch() returns a promise, so we can chain this to get the response out:
A: Static methods include Promise.all(), Promise.allSettled(), Promise.any(), Promise.race(), Promise.reject(), and Promise.resolve(). These methods provide various ways to handle multiple Promises and create new Promises. Instance methods include Promise.prototype.then(), Promise.prototype.catch(), and Promise.prototype.finally(). These methods are used to handle the value and errors of a Promise and perform actions when the Promise settles.
- Promises are exactly what you would expect from its name. They make a promise that a response will be sent to the browser. Meanwhile, other tasks can be performed.
- Promises can have four states - PENDING, FULFILLED, REJECTED, and SETTLED.
- When a promise is triggered, it is in the Pending state. After some time, this asynchronous task might be completed and return with one of these statuses - fulfilled or rejected.
- A promise fulfilled or rejected is said to be in the SETTLED state. Once settled, a promise cannot be resettled.
- How does the browser get to know when a promise gets a response? We have the following methods for the same.