JavaScript tutorials > Advanced Concepts > Asynchronous JavaScript > What is a Promise in JavaScript?
What is a Promise in JavaScript?
Promises in JavaScript are a powerful tool for handling asynchronous operations. They provide a cleaner and more manageable alternative to traditional callback functions, especially when dealing with complex asynchronous workflows. This tutorial will delve into the core concepts of Promises, explore their practical applications, and outline best practices for effective utilization.
Promise Basics
A Promise is an object representing the eventual completion (or failure) of an asynchronous operation. It essentially acts as a placeholder for a value that isn't yet known when the promise is created. A Promise can be in one of three states:
Creating a Promise
To create a Promise, you use the Inside the executor function, you perform your asynchronous operation. Based on the outcome, you call either new Promise()
constructor. This constructor takes a function as an argument called the executor function. The executor function receives two arguments: resolve
and reject
.
resolve
: A function that, when called, transitions the Promise to the 'fulfilled' state with the provided value.reject
: A function that, when called, transitions the Promise to the 'rejected' state with the provided reason.resolve
or reject
to signal the completion or failure of the operation, respectively.
const myPromise = new Promise((resolve, reject) => {
// Asynchronous operation (e.g., fetching data)
setTimeout(() => {
const success = true; // Simulate success or failure
if (success) {
resolve('Operation completed successfully!'); // Resolve with a value
} else {
reject('Operation failed!'); // Reject with a reason
}
}, 1000); // Simulate 1 second delay
});
Consuming a Promise: .then()
, .catch()
, and .finally()
Once you have a Promise, you can use the .then()
, .catch()
, and .finally()
methods to handle the results:
.then(onFulfilled, onRejected)
: Takes two optional arguments:
onFulfilled
: A function to be executed when the Promise is fulfilled. It receives the resolved value as an argument.onRejected
: A function to be executed when the Promise is rejected. It receives the rejection reason as an argument. If onRejected
is not provided, the rejection is propagated to the next .catch()
handler..catch(onRejected)
: A shorthand for .then(null, onRejected)
. It's specifically designed to handle rejections.
.finally(onFinally)
: A function that will be executed regardless of whether the Promise was fulfilled or rejected. It's often used for cleanup tasks, such as hiding a loading indicator.
myPromise
.then(value => {
console.log('Success:', value); // Handle the resolved value
})
.catch(error => {
console.error('Error:', error); // Handle the rejection reason
})
.finally(() => {
console.log('Finally: This will always execute.'); // Execute after the promise is settled (either resolved or rejected)
});
Promise Chaining
Promises can be chained together using .then()
. When a .then()
handler returns a Promise, the next .then()
handler in the chain will wait for that Promise to resolve before executing. This allows you to create a sequence of asynchronous operations that execute in a specific order. If any Promise in the chain rejects, the rejection will be propagated down the chain until it reaches a .catch()
handler.
function fetchData(url) {
return new Promise((resolve, reject) => {
// Simulate fetching data (replace with actual fetch API call)
setTimeout(() => {
const data = `Data from ${url}`; // Simulated data
resolve(data);
}, 500);
});
}
fetchData('api/endpoint1')
.then(data1 => {
console.log('Data 1:', data1);
return fetchData('api/endpoint2'); // Return another Promise
})
.then(data2 => {
console.log('Data 2:', data2);
return fetchData('api/endpoint3'); // Return another Promise
})
.then(data3 => {
console.log('Data 3:', data3);
})
.catch(error => {
console.error('Error:', error);
});
Promise.all()
Promise.all()
takes an array of Promises as input and returns a single Promise that resolves when all of the input Promises have resolved. The resolved value of the returned Promise is an array containing the resolved values of the input Promises, in the same order as they were provided. If any of the input Promises reject, the returned Promise will immediately reject with the rejection reason of the first rejected Promise.
const promise1 = Promise.resolve(1);
const promise2 = new Promise((resolve, reject) => setTimeout(resolve, 100, 'foo'));
const promise3 = 42;
Promise.all([promise1, promise2, promise3])
.then(values => {
console.log(values); // Output: [1, 'foo', 42]
});
Promise.allSettled()
Promise.allSettled()
takes an array of Promises as input and returns a single Promise that resolves when all of the input Promises have either resolved or rejected. The resolved value of the returned Promise is an array of objects, where each object describes the outcome of a Promise.
const promise1 = Promise.resolve(1);
const promise2 = Promise.reject(2);
const promise3 = new Promise((resolve) => setTimeout(() => resolve(3), 100));
Promise.allSettled([promise1, promise2, promise3])
.then((results) => results.forEach((result) => console.log(result.status)));
// Expected output:
// "fulfilled"
// "rejected"
// "fulfilled"
Promise.race()
Promise.race()
takes an array of Promises as input and returns a single Promise that resolves or rejects as soon as one of the Promises in the array resolves or rejects, with the value or rejection reason from that Promise.
const promise1 = new Promise((resolve, reject) => {
setTimeout(resolve, 500, 'one');
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'two');
});
Promise.race([promise1, promise2]).then((value) => {
console.log(value);
// Both resolve, but promise2 is faster
});
// Expected output: "two"
Promise.any()
Promise.any()
takes an array of Promises as input and returns a single Promise that resolves as soon as one of the Promises in the array resolves. If all of the input Promises reject, the returned Promise will reject with an AggregateError, containing the rejection reasons of all the rejected Promises.
const promise1 = Promise.reject(0);
const promise2 = new Promise((resolve) => setTimeout(resolve, 100, 'quick'));
const promise3 = new Promise((resolve) => setTimeout(resolve, 500, 'slow'));
Promise.any([promise1, promise2, promise3]).then((value) => console.log(value));
// Expected output: "quick"
Concepts Behind the Snippet
Promises encapsulate the eventual result of an asynchronous operation, providing a way to handle success (resolve) or failure (reject) in a structured manner. They help avoid callback hell by enabling cleaner and more readable asynchronous code through chaining.
Real-Life Use Case
Fetching data from an API is a common use case for Promises. Imagine retrieving user profile information. The fetch()
API returns a Promise that resolves with the response. You can then chain .then()
calls to parse the response body as JSON and process the data. Error handling is done with .catch()
to manage network errors or invalid responses.
Best Practices
.catch()
handler at the end of your Promise chains to prevent unhandled rejections from crashing your application.async/await
provides a more synchronous-looking syntax for working with Promises, which can improve readability.
Interview Tip
Be prepared to explain the different states of a Promise (pending, fulfilled, rejected) and how to handle each state using .then()
and .catch()
. Also, understand the difference between Promise.all()
, Promise.race()
, Promise.allSettled()
and Promise.any()
and when you would use each one.
When to Use Them
Use Promises whenever you're dealing with asynchronous operations, such as:
Memory Footprint
Promises themselves introduce a small memory overhead, as they are objects that need to be stored in memory. However, the benefits of using Promises for managing asynchronous operations (improved code readability, error handling) generally outweigh the minor memory cost.
Alternatives
Alternatives to Promises include:
Pros
Cons
FAQ
-
What happens if I don't include a
.catch()
handler?
If a Promise rejects and there is no
.catch()
handler to handle the rejection, the rejection will propagate up the call stack. In most JavaScript environments (browsers, Node.js), this will result in an unhandled rejection error being logged to the console. In Node.js, unhandled rejections can even crash your application if not properly handled. -
Can I use
async/await
with Promises?
Yes,
async/await
is syntactic sugar over Promises. It provides a more synchronous-looking way to write asynchronous code, making it easier to read and understand. You can useasync/await
with any function that returns a Promise. -
What is the difference between
Promise.all()
andPromise.allSettled()
?
Promise.all()
rejects if any of the input promises reject, whilePromise.allSettled()
always resolves, providing information about the status (fulfilled or rejected) of each input promise.