JavaScript Promise
Written By: Avinash Malhotra
Updated on
JS Promises
JavaScript Promises are a modern approach to handle asynchronous operations. Although we can handle small asynchronous operations using Callback Functions, but not large operations. Promises can handle long and nested asynchronous operations easily.
Promises are also helpful to handle long chaining of nested callback functions known as callback hell in JavaScript.
Built-in APIs Using Promises
Many modern JavaScript APIs return promises natively, making async operations more manageable:
- Fetch API - for HTTP requests
- Battery API (getBattery)
What is Callback Hell?
Callback Hell (also called Pyramid of Doom) occurs when we nest multiple callbacks inside each other to perform sequential asynchronous operations. This makes code hard to read, maintain, and debug. Promises provide a cleaner solution for managing asynchronous flows.
Output:
app running
Callback executed after timer
const callbackHell = (callback) => {
setTimeout(() => {
console.log("second");
callback();
}, 1000);
};
callbackHell(() => {
console.log("Callback executed after timer");
});
console.log('app running'); // Executes first (async nature)
Why Promises Are Better: Promises eliminate nested callbacks and allow chaining with .then(), making code more readable and maintainable.
Understanding Promise States
A Promise object always exists in one of three states during its lifecycle: pending, fulfilled, or rejected. Once a promise transitions from pending to either fulfilled or rejected, it is considered settled and cannot change states. To handle the outcomes, use .then() for success and .catch() for errors.
| Promise States | Explanation |
|---|---|
| pending | The initial state, waiting for fulfill or rejection |
| fulfilled | the operation has been successfully completed |
| rejected | the operation failed |
A Promise is said to be resolved or settled when it is fulfilled or rejected. Once settled (resolved or rejected), a Promise cannot be changed. That means a Promise is immutable after settled.
The then(), catch() and finally methods of Promise are used to handle callbacks that executes when it is settled or resolved.
Promise.then() Method
The Promise.then() method handles fulfilled promises. It accepts up to two callback functions: one for success and one for errors. The method always returns a new promise, enabling method chaining.
Syntax: promise.then(onFulfilled, onRejected)
done
function done(){
console.log("done");
}
function error(){
console.error("error");
}
const promise=new Promise((resolve,reject)=>{
resolve();
reject();
});
promise.then(done).catch(error);
Promise with timer
Use a timer in promise to delay output .
const promise=new Promise((resolve)=>{
setTimeout(()=>{
resolve("promise resolved after 1 sec");
},1000);
});
promise.then(i=>console.log(i));
Promise.catch() Method
The Promise.catch() method is used to handle rejected promises or errors that occur during promise execution. It's equivalent to calling .then(null, onRejected) and is essential for error handling in async operations.
Syntax: promise.catch(onRejected)
ReferenceError: age is not defined
const promise = new Promise((resolve, reject) => {
try {
let undefinedVar;
console.log(undefinedVar.property); // Throws error
resolve("Success");
} catch(err) {
reject(err);
}
});
promise
.then(result => console.log(result))
.catch(error => console.error("Caught error:", error.message));
In the example above, we create a promise that intentionally throws an error by trying to access a property on undefined. The .catch() method intercepts this error and handles it gracefully. This is more reliable than using only .then() with error handling.
Best Practice: Always use .catch() at the end of a promise chain to handle any errors that occur in previous steps. This prevents unhandled promise rejections.
Promise.resolve() Method
The Promise.resolve() method returns a promise that is resolved with the given value. If the value is already a promise, it returns that promise. This is useful for converting values into promises or immediately returning resolved values in async functions.
Syntax: Promise.resolve(value)
Promise {<fulfilled>: 1}
let x=Promise.resolve(1);
console.log(x);
3
hello
let x=Promise.resolve('hello');
x.then(i=>console.log(i));
let y=3;
console.log(y);
Promise.reject() Method
The Promise.reject() method returns a promise that is immediately rejected with the given reason. Use this method when you need to immediately fail a promise, typically in error handling scenarios.
Syntax: Promise.reject(reason)
Promise {<rejected>: 1}
let x=Promise.reject(1);
console.log(x);
Promise Concurrency Methods
When working with multiple promises simultaneously, the Promise class provides four powerful methods to manage them. These methods differ in how they handle fulfilled and rejected promises, so choosing the right one is crucial for your use case:
Promise.all() Method
Promise.all() takes an iterable (typically an array) of promises and returns a single promise. It fulfills when ALL promises in the array are fulfilled, returning an array of their resolved values in the same order. If ANY promise rejects, the entire Promise.all() immediately rejects with that reason.
Use Case: Perfect when you need all operations to succeed before proceeding (e.g., loading multiple required resources).
Syntax: Promise.all([promise1, promise2, ...])
[1,2,3]
const x=Promise.resolve(1);
const y=Promise.resolve(2);
const z=Promise.resolve(3);
const arr=[x,y,z];
Promise.all(arr).then(i=>{console.log(i)});
Promise.allSettled() Method
Promise.allSettled() takes an iterable of promises and returns a single promise that settles AFTER all promises have settled (either fulfilled or rejected). It returns an array of objects describing each promise's outcome, regardless of success or failure.
Use Case: Ideal when you want to know the result of all operations, regardless of whether they succeed or fail (e.g., testing multiple APIs).
Syntax: Promise.allSettled([promise1, promise2, ...])
Key Difference from Promise.all: allSettled() never rejects; it waits for all promises to complete and returns their results, while all() fails fast on the first rejection.
[{status: 'fulfilled', value: 1}, {status: 'fulfilled', value: 2}, {status: 'fulfilled', value: 3}]
const x=Promise.resolve(1);
const y=Promise.resolve(2);
const z=Promise.resolve(3);
const p=[x,y,z];
Promise.allSettled(p).then(i=>console.log(i));
Promise.allSettled with reject
[{status: 'fulfilled', value: 1}, {status: '"rejected"', reason: 2}, {status: 'rejected', reason: 3}]
const x=Promise.resolve(1);
const y=Promise.resolve(2);
const z=Promise.reject(3);
const p=[x,y,z];
Promise.allSettled(p).then(i=>console.log(i));Iterate over Promise.allSettled
{status: 'fulfilled', value: 1}
{status: 'fulfilled', value: 2}
{status: 'fulfilled', value: 3}]
const x=Promise.resolve(1);
const y=Promise.resolve(2);
const z=Promise.resolve(3);
const p=[x,y,z];
Promise.allSettled(p).then(i=>{
for( let j of i ){
console.log(j);
}
});
Promise.any() Method
Promise.any() takes an iterable of promises and returns a single promise that fulfills as soon as ANY ONE promise in the iterable fulfills. It returns the value of the first resolved promise. If all promises reject, Promise.any() rejects with an AggregateError.
Use Case: Perfect for scenarios where you need just one successful result from multiple attempts (e.g., fetching from multiple backup servers).
Syntax: Promise.any([promise1, promise2, ...])
1
const x=Promise.resolve(1);
const y=Promise.resolve(2);
const z=Promise.resolve(3);
const arr=[x,y,z];
Promise.any(arr).then(i=>{console.log(i)});
2
const x=Promise.reject(1);
const y=Promise.resolve(2);
const z=Promise.resolve(3);
const arr=[x,y,z];
Promise.any(arr).then(i=>{console.log(i)});
Promise.race() Method
Promise.race() takes an iterable of promises and returns a single promise that settles as soon as ANY promise in the iterable settles (either fulfills or rejects). It adopts the value or rejection reason of the first settled promise. It literally "races" the promises to see which completes first.
Use Case: Useful for timeouts, implementing race conditions, or fallback scenarios (e.g., race a fetch request against a timeout promise).
Syntax: Promise.race([promise1, promise2, ...])
promise 2
const p1 = new Promise((resolve, reject) => {
setTimeout(resolve, 500, 'promise 1');
});
const p2 = new Promise((resolve, reject) => {
setTimeout(resolve, 300, 'promise 2');
});
Promise.race([p1, p2]).then((value) => {
console.log(value);
});
Promise.finally() Method
Promise.finally() executes a callback function after a promise is settled, regardless of whether it was fulfilled or rejected. Unlike .then() and .catch(), it doesn't receive any value or reason, making it perfect for cleanup tasks.
Use Case: Hide loading spinners, close connections, or perform cleanup operations that should happen regardless of the outcome.
Output:
Loading...
Data received
Loading complete
const promise = new Promise((resolve) => {
setTimeout(() => resolve("Data received"), 1000);
});
console.log("Loading...");
promise
.then(data => console.log(data))
.catch(error => console.error(error))
.finally(() => console.log("Loading complete"));
Promise Best Practices
- Always handle errors: Use .catch() or .finally() to prevent unhandled rejections
- Avoid nested promises: Use .then() chaining instead of nesting promises
- Choose the right concurrency method: Promise.all() for all success, allSettled() for all results, any() for first success, race() for first completion
- Use async/await for readability: Modern alternative to promise chains for cleaner code
- Avoid creating unnecessary promises: Don't wrap already-resolved values in new promises
- Be aware of promise state: Once settled, a promise cannot change its state
Summary
JavaScript Promises form the foundation of modern asynchronous programming. By understanding promise states (pending, fulfilled, rejected), core methods (.then(), .catch(), .finally()), and utility methods (Promise.all(), allSettled(), any(), race()), you can write clean, maintainable async code and effectively handle complex workflows. Promises enable you to avoid callback hell and write more readable, testable code. Mastering promises is essential for modern JavaScript development, especially when working with APIs, timers, and other asynchronous operations.