Deep Dive into Promises in JavaScript: Understanding Promises, Chaining, Error Handling, and Best Practices

Pradip Nemane
5 min readFeb 22, 2023

Promises are an important feature of modern JavaScript that allow developers to handle asynchronous operations in a more efficient and readable way. In this blog, we’ll take a deep dive into the world of promises and explore some advanced techniques for working with them in JavaScript.

What are Promises?

Promises are objects in JavaScript that represent the eventual completion or failure of an asynchronous operation and allow you to handle the result of that operation when it is ready. Promises have three states:

  • Pending: The initial state of a promise when it is created.
  • Fulfilled: The state of a promise when the asynchronous operation completes successfully.
  • Rejected: The state of a promise when the asynchronous operation fails.

Promises allow you to chain asynchronous operations and handle errors in a more organized way than using callbacks.

Here’s an example of how to create and use a Promise in JavaScript:

const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
const randomValue = Math.random();
if (randomValue > 0.5) {
resolve(randomValue);
} else {
reject(new Error('Random value is too low!'));
}
}, 1000);
});

myPromise.then((result) => {
console.log(`Promise was fulfilled with result: ${result}`);
}).catch((error) => {
console.error(`Promise was rejected with error: ${error}`);
});

In this example, we create a new Promise object that takes a function as an argument. The function receives two parameters, resolve and reject, which are functions that you can use to fulfill or reject the promise when the asynchronous operation is complete.

In this case, we use setTimeout to simulate an asynchronous operation that takes one second to complete. We generate a random number, and if it is greater than 0.5, we fulfill the promise with that value. Otherwise, we reject the promise with an error object.

We then use the then method to handle the fulfillment of the promise by logging the result to the console. If the promise is rejected, we handle the error using the catch method.

Chaining Promises

Promises can also be chained together to perform a series of asynchronous operations. This can be done by returning a new Promise from the then method.

Here’s an example of how to chain promises in JavaScript:

const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(10);
}, 1000);
});

myPromise.then((result) => {
console.log(`First result: ${result}`);
return result * 2;
}).then((result) => {
console.log(`Second result: ${result}`);
return result - 4;
}).then((result) => {
console.log(`Third result: ${result}`);
}).catch((error) => {
console.error(`Promise was rejected with error: ${error}`);
});

In this example, we create a new Promise that fulfills after one second with the value 10. We then use the then method to perform three operations on that value: doubling it, subtracting 4, and logging the final result to the console.

If any of the then methods throw an error, the catch method will be called with the error object.

Advanced Promise Techniques

There are a few more advanced techniques that you can use with promises to handle more complex scenarios in JavaScript.

Promise.race

The Promise.race method is similar to Promise.all, but instead of waiting for all promises to complete, it waits for the first promise to either fulfill or reject. The method takes an array of promises as an argument and returns a new promise that fulfills with the result of the first promise to complete.

const promises = [
new Promise((resolve) => setTimeout(() => resolve(1), 1000)),
new Promise((resolve) => setTimeout(() => resolve(2), 500)),
new Promise((resolve) => setTimeout(() => resolve(3), 2000)),
];

Promise.race(promises).then((result) => {
console.log(`The first promise to complete returned ${result}`);
});

In this example, we create an array of three promises, each of which fulfills after a different amount of time. We then use Promise.race to wait for the first promise to complete and log its result to the console.

Promise.all

The Promise.all method takes an array of promises as an argument and returns a new promise that fulfills with an array of the results when all of the input promises have fulfilled, or rejects with the reason of the first promise to reject.

const promises = [
new Promise((resolve) => setTimeout(() => resolve(1), 1000)),
new Promise((resolve) => setTimeout(() => resolve(2), 2000)),
new Promise((resolve) => setTimeout(() => resolve(3), 3000)),
];

Promise.all(promises).then((values) => {
console.log(`All promises resolved: ${values}`);
});

In this example, we create an array of three promises, each of which fulfills after a different amount of time. We then use Promise.all to wait for all of the promises to complete and log their results to the console.

Promise.any

The Promise.any method is similar to Promise.race, but instead of waiting for the first promise to complete, it waits for the first promise to fulfill. If all promises are rejected, the method throws an AggregateError that contains all of the rejection reasons.

const promises = [
new Promise((resolve) => setTimeout(() => resolve(1), 1000)),
new Promise((resolve, reject) => setTimeout(() => reject(new Error('Failed')), 500)),
new Promise((resolve) => setTimeout(() => resolve(3), 2000)),
];

Promise.any(promises).then((result) => {
console.log(`The first promise to fulfill returned ${result}`);
}).catch((error) => {
console.error(`All promises were rejected: ${error}`);
});

In this example, we create an array of three promises, one of which is rejected. We then use Promise.any to wait for the first promise to fulfill and log its result to the console. If all promises are rejected, we catch the error and log a message to the console.

Conclusion

Promises are an important feature of modern JavaScript that allow you to handle asynchronous operations in a more efficient and readable way. They can be used to chain asynchronous operations, run multiple promises in parallel, and handle errors in a more organized way.

By understanding these advanced techniques for working with promises, you can take your asynchronous JavaScript code to the next level and write more efficient, readable, and maintainable code.

--

--

Pradip Nemane

Hey, I am a Full-Stack Developer, Works on Javascript, Angular, NodeJS, Java, and SpringBoot | MEAN Stack | UI (Angular / ReactJS) | J2EE