In JavaScript, a Promise
is an object that represents the eventual completion (or failure) of an asynchronous operation and its resulting value. Promises provide a much cleaner and more readable way to handle asynchronous operations compared to callbacks, especially when you have several asynchronous tasks that depend on each other.
In the previous topic, we learned that callbacks can sometimes lead to messy code when we need to deal with multiple asynchronous operations, resulting in "callback hell." Promises solve this problem by allowing us to structure our code in a more manageable way. With Promises, we can avoid deeply nested callbacks and write asynchronous code that is easier to read and understand.
A Promise
in JavaScript can be thought of as a guarantee that we will get a result in the future. The result can either be:
Think of it like ordering food from a restaurant. When you place an order, you're "promised" that your food will arrive. If everything goes well, you get your meal (the promise is resolved
). If something goes wrong (e.g., they run out of ingredients), they notify you that the order can’t be fulfilled (the promise is rejected
).
A Promise can be in one of three states:
PENDING
: The initial state, when the promise is neither resolved nor rejected.FULFILLED
(or RESOLVED
): The operation has completed successfully, and we have the result.REJECTED
: The operation failed, and we get an error or failure reason.We create a promise using the Promise
constructor, which takes a function as an argument. This function itself takes two arguments:
resolve
: A function that is called when the operation completes successfully.reject
: A function that is called if there’s an error or failure.Here’s a simple example of creating a promise:
const myPromise = new Promise((resolve, reject) => {
const success = true; // Imagine this is the result of an asynchronous task
if (success) {
resolve('Operation was successful!');
} else {
reject('There was an error.');
}
});
In this example, the promise checks if success
is true. If it is, the promise is resolved with the message "Operation was successful!" Otherwise, it’s rejected with the message "There was an error."
Once a promise is created, we need a way to handle the result. We use two key methods:
.then()
: This is called when the promise is resolved successfully..catch()
: This is called when the promise is rejected (i.e., there’s an error).Here’s how we handle the promise from the previous example:
myPromise
.then((message) => {
console.log(message); // This runs if the promise is resolved
})
.catch((error) => {
console.log(error); // This runs if the promise is rejected
});
In this example:
.then()
method logs the success message ("Operation was successful!")..catch()
method logs the error message ("There was an error.").Let’s see a more practical example using a mock asynchronous task, like fetching data from a server:
function fetchData() {
return new Promise((resolve, reject) => {
const dataReceived = true; // Simulating a successful data fetch
setTimeout(() => {
if (dataReceived) {
resolve('Data received successfully!');
} else {
reject('Failed to fetch data.');
}
}, 2000); // Simulating a 2-second delay
});
}
fetchData()
.then((data) => {
console.log(data); // This will run if data is successfully fetched
})
.catch((error) => {
console.log(error); // This will run if there's an error
});
In this example:
fetchData
that returns a promise.setTimeout
to mimic a delay (2 seconds) before resolving or rejecting the promise.One of the key benefits of promises is chaining
. You can link .then()
calls together to handle multiple asynchronous tasks in a sequence. For example:
fetchData()
.then((data) => {
console.log(data); // Log the fetched data
return 'Next operation'; // Pass the result to the next then()
})
.then((message) => {
console.log(message); // Log the next message
})
.catch((error) => {
console.log(error); // Catch any error that occurs in any step
});
Here, the first .then()
gets the fetched data, and the second .then()
uses the result from the first one. If any error occurs, the .catch()
will handle it.
Think of promises like placing an online order. You place the order, and you are promised the delivery of the product (which might take some time). The promise has two possible outcomes: either the delivery is successful (resolved) and you get your product, or there’s a problem with the delivery (rejected) and you get an error notification. You don’t wait by the door the whole time; instead, you’re notified when the delivery is done or if something goes wrong.
Promise
represents the eventual result of an asynchronous operation. It can either be resolved (success) or rejected (failure)..then()
to handle the successful result of a promise and .catch()
to handle any errors.Promises are a powerful tool for managing asynchronous code in JavaScript. But as we move forward, there's an even simpler way to write asynchronous code using async
and await
. We’ll cover that next!