Here's what we'll cover:
- How a Promise Works
- Callbacks vs Promises
- When to Use Promises Instead of Callbacks
- Promises and the Fetch API
How a Promise Works
Promise object represents a “pending state” in the most common sense: the promise will eventually be fulfilled at a later date.
To give you an illustration, suppose you want to buy a new phone to replace your old phone, so you open a messaging app to contact a phone store. This is similar to how you access a variable or a function that returns a promise:
After you send a message explaining what you want, you get an automated message saying that a representative will answer your message shortly. This is similar to receiving a Promise object:
A minute later, you get a new message from a human representative, saying that the phone model you want to buy is available for purchase. This is when the Promise was resolved:
Or, in a completely different scenario, the representative tells you that the store doesn’t sell phones, because the store is a food store and not a phone store. This means the Promise was rejected:
This illustration shows how the
- A Promise is like the automated message that we saw earlier. It represents a pending state that must be fulfilled at some point later.
- The human representative saying that the phone model is available is similar to the
resolve()method, which shows that the Promise is fulfilled.
- The representative telling you that you’re contacting the wrong store is like the
reject()method, which is the method used to show that the Promise can’t be fulfilled because of an error.
A typical promise implementation looks like this:
When creating a new Promise object, we need to pass a callback function that will be called immediately with two arguments: the
Depending on the result of the
Promise, either the
resolve() or the
reject() function will be called to end the pending state.
To handle the
Promise object, you need to chain the function call with the
catch() functions as shown below:
resolve() function corresponds to the
then() function, while
reject() corresponds to the
catch() function. You can change the
isTrue value to
false to test this.
Here’s an illustration of the promise process:
Using the promise pattern, you can call your functions sequentially by placing the next process inside the
Callbacks vs Promises
The promise pattern was created to replace the use of callbacks in certain situations. By using promises, the code we write is more intuitive and maintainable.
Going back to the messaging illustration, let’s create an example of using callbacks to handle the situation.
First, we declare the two variables required for this situation, called
Next, we write a function that will process incoming messages. This function will mimic the promise pattern, and it will resolve only when
Here, you can see that the function
processMessage accepts two callback functions:
When we call the function, we need to provide the callback functions, similar to how we need to chain the
catch() methods when accessing a promise:
value => console.log(value),
reason => console.log(reason)
In the call to
processMessage above, the first argument is the
resolveCallback() function, and the second argument is the
If you run the code above, then the
resolveCallback() function will be called. You can change one of the two variables to
false to trigger the
Now that we have a working callback example, let’s rewrite the code using a promise as follows:
Here, you can see that the
processMessage() function returns a
Promise object that gets resolved only when both
When one of the two variables is
false, then the
Promise object will be rejected.
Here you can see that you don’t need to add two extra parameters to the
processMessage() function just for the callbacks. Also, when calling the function, you use the
catch() methods to handle the result of the promise.
The use of a promise makes the code easier to understand. Here’s the comparison of the two side by side:
I don’t know about you, but I sure love writing and reading the promise pattern more than the callback pattern. 😉
When to Use Promises Instead of Callbacks
As I've mentioned before, the promise object is created to replace callback functions in certain situations.
And if you examine the code for the promise object above closely, you'll see that even promises use callbacks inside the
catch() methods. This means Promises don't eliminate the need for callbacks.
Promises are used when you need to wait for a certain task to finish before running the next process.
For example, suppose you have three functions that need to run sequentially from one to three.
Each function runs for a few seconds. We simulate this using the
Using callbacks, you can define parameters on the
stepTwo() functions, then call those functions sequentially like this:
The nested callbacks where you pass the next function inside the previous function is famously known as the "callback hell". This code pattern is hard to read and it's messy.
With promises, you can rewrite the code above as follows:
Here, you can see that each function returns a promise that resolves when the timeout is finished. The function calls using
then() methods maintain a clear order of steps.
In a real-world project where you have many lines of code inside the callback functions, using Promises provides a massive gain in your ability to read, extend, and maintain the code.
But if you're running code that doesn't have to wait for certain processes, then you can use callbacks just fine.
For example, the array methods like
forEach() use callbacks, so there's no need for promises there:
Another use of promises is when you use the Fetch API, which is used to run network requests. Let's see how that works now.
Promises and the Fetch API
The Fetch API always returns a
Promise object, so you need to handle it using the
catch() methods as shown below:
fetch('<Your API URL>')
.then(response => console.log(response))
.catch(error => console.log(error));
If you run a
fetch() function and assign the result to a variable, the variable will contain a
As you can see, the
Promise object is assigned to the
response variable in a pending state. If you wait a while and then log the object again, the output will be fulfilled:
The Fetch API will return a
Response object when the promise is fulfilled. This is also why I usually name the parameter inside the
then() method as
response . Feel free to name the response as
value , or anything your team agreed on.
Now that you’ve learned how the
Promise object works, it’s time to learn some extra methods related to this object.
Promise objects. For example, suppose you deal with three different promises in your project like this:
Now, suppose you need all three promises to resolve before moving to the next step. Knowing what we know about promises, we can chain the promises using the
then() method like this:
In this example, each
then() method returns another promise, creating nested callbacks. When the
p3 promise is resolved, the messages are returned as a single array.
then() method would then log the
messages array returned by the promises. This approach works, but this is exactly the type of code we want to avoid when using promises.
Instead of using nested callbacks, we can use the
Promise.all() method instead. See the example below:
Promise.all() method accepts an array of promises, and when all promises are resolved, the method will pass the
messages returned by the promises as an array and pass it to the
If one of the promises is rejected, then the method returns the first rejection it encounters and stops any further process.
This method enables you to work with many promises without having to create nested callbacks. You should use this method when you need all promises to resolve.
Promise.allSettled() method is similar to the
Promise.all() method, but instead of proceeding to
catch() when one of the promises got rejected, the method will store the reject result and continue processing other promises.
When all promises are settled, the method will return an array of objects that contains the details of each promise. For example, suppose you run the following code:
Then the result would be:
As you can see, the
response variable is an array of objects showing the status and the value or reason for that status. When calling this method, you don't need to chain the
You should use this method when you always need to know the result of each promise.
Promise.any() method is similar to the
Promise.all() method, except that it returns only a single value from any promise that calls the
resolve() function first. If you try the method as follows:
The output will be:
This is because the first promise is rejected, and once the second promise is resolved, the
any() method stops any further execution of promises and returns the resolved value.
This method returns an error only when all promises are rejected. You should use this method only when you need to get a single promise resolved out of many promises.
Promise.race() method is like the
Promise.any() method, with one difference: the promise is settled when any promise is resolved or rejected:
p1 returns a rejection, then the
Promise.race() method returns the rejection instead of continuing the process:
You should use this method only when you need to get a single promise to settle, no matter if the result is resolved or rejected.
As you can see, these four methods of the
Promise object provides you with a powerful composition tool that helps you decide how to handle multiple promises in your project.
And now you’ve learned how the
You’ve also learned how promises can be used to replace callbacks, when to use promises instead of callbacks, and how to use promise methods when you need to handle many promises in your project.
Until next time!