Understanding and Mastering JavaScript Promises

In JavaScript, a Promise is an object that represents the eventual completion or failure of an asynchronous operation and its resulting value. Promises are used to handle asynchronous operations more elegantly and avoid callback hell.

Basic Structure:

A Promise has three states:

  1. Pending: The initial state; the promise is neither fulfilled nor rejected.
  2. Fulfilled: The operation completed successfully, and the promise has a resulting value.
  3. Rejected: The operation failed, and the promise has a reason for the failure.

Creating a Promise:

You can create a promise using the Promise constructor, which takes a function as an argument. This function, called the executor, takes two parameters: resolve and reject.

const myPromise = new Promise((resolve, reject) => {
  // Asynchronous operation
  if (/* operation is successful */) {
    resolve("Operation succeeded!");
  } else {
    reject("Operation failed!");
  }
});

Using Promises:

You can then handle the result of the Promise using the .then() and .catch() methods. The .then() method is called when the Promise is fulfilled, and the .catch() method is called when the Promise is rejected.

myPromise
  .then((result) => {
    console.log(result); // Operation succeeded!
  })
  .catch((error) => {
    console.error(error); // Operation failed!
  });

Chaining Promises:

Promises can be chained together using .then() to handle multiple asynchronous operations sequentially.

const firstPromise = new Promise((resolve) => {
  resolve("First operation succeeded!");
});

const secondPromise = (message) => {
  return new Promise((resolve) => {
    resolve(`${message} Second operation succeeded!`);
  });
};

firstPromise
  .then((result) => {
    return secondPromise(result);
  })
  .then((result) => {
    console.log(result); // First operation succeeded! Second operation succeeded!
  })
  .catch((error) => {
    console.error(error);
  });

Promise.all():

Promise.all() is used when you have multiple promises and want to wait for all of them to complete.

const promise1 = Promise.resolve("Promise 1");
const promise2 = new Promise((resolve) => setTimeout(() => resolve("Promise 2"), 1000));
const promise3 = fetch('https://api.example.com/data');

Promise.all([promise1, promise2, promise3])
  .then((results) => {
    console.log(results); // ["Promise 1", "Promise 2", response from fetch]
  })
  .catch((error) => {
    console.error(error);
  });

Promise.race():

Promise.race() is used when you have multiple promises and want to resolve or reject as soon as one of them resolves or rejects.

const promise1 = new Promise((resolve) => setTimeout(() => resolve("Promise 1"), 500));
const promise2 = new Promise((resolve, reject) => setTimeout(() => reject("Promise 2"), 1000));

Promise.race([promise1, promise2])
  .then((result) => {
    console.log(result); // Promise 1
  })
  .catch((error) => {
    console.error(error); // Promise 2
  });

Promise.allSettled():

Promise.allSettled() is a method introduced in ES2020 that takes an array of promises and returns a new promise. This new promise is fulfilled with an array of results when all the input promises have settled, regardless of whether they fulfilled or rejected.

const promise1 = Promise.resolve("Resolved Promise");
const promise2 = Promise.reject("Rejected Promise");
const promise3 = new Promise((resolve) => setTimeout(() => resolve("Delayed Promise"), 1000));

Promise.allSettled([promise1, promise2, promise3])
  .then((results) => {
    console.log(results);
    /*
    [
      { status: 'fulfilled', value: 'Resolved Promise' },
      { status: 'rejected', reason: 'Rejected Promise' },
      { status: 'fulfilled', value: 'Delayed Promise' }
    ]
    */
  })
  .catch((error) => {
    console.error(error);
  });

Promise.allSettled() is useful when you want to know the outcome of all promises, regardless of whether they were fulfilled or rejected. The results include an object for each promise with a status (‘fulfilled’ or ‘rejected’) and the corresponding value or reason.

Conclusion

Promises provide a cleaner way to handle asynchronous operations in JavaScript, and they become even more convenient when used with async/await. They help avoid callback hell and make code more readable and maintainable.