Understanding Promise Methods in JavaScript

Understanding Promise Methods in JavaScript

·

12 min read

Introduction

Promises are a powerful feature in JavaScript for managing asynchronous operations. They provide a clean and readable way to handle asynchronous tasks, making your code more efficient and maintainable. JavaScript offers various promise-related methods to manage and coordinate multiple promises. We will delve into four of these methods: Promise.all(), Promise.allSettled(), Promise.race(), and a hypothetical Promise.any().

1. Promise.all()

Promise.all() is a method used to execute multiple promises in parallel and wait for all of them to get settled. It takes an iterable, typically an array, containing multiple promises as its argument, and Promise.all() returns a new promise.

Example 1:

Suppose you have three asynchronous functions that return promises in which all promises gets resolved:

// Define a function pR that returns a Promise.
const promiseFunction= (timer, flag, name) => {
  return new Promise((resolve, reject) => {
    // Simulate an asynchronous task with a time delay.
    setTimeout(() => {
      if (flag) {
        // If the 'flag' is true, resolve the promise with a success message.
        resolve({ msg: `Successful ${timer} ${name}` });
      } else {
        // If the 'flag' is false, reject the promise with a failure message.
        reject({ msg: `Failed ${timer} ${name}` });
      }
    }, timer);
  });
};

//Will be using this as promise function.
// Define an asynchronous function 'main'.
async function main() {
  try {
    // Create three promises with different delays and conditions.
    let p1 = promiseFunction(2000, true, "one");
    let p2 = promiseFunction(5000, true, "two");
    let p3 = promiseFunction(1000, true, "three");

    let results = await Promise.all([p1, p2, p3]);

    // Log the result of the all resolved promise.
    console.log(results, "results");
  } catch (e) {
    // If all promises reject, handle the error by logging it.
    console.log(e, "error");
  }
}

main();
// console after 5 seconds
[
  { msg: 'Successful 2000 one' },
  { msg: 'Successful 5000 two' },
  { msg: 'Successful 1000 three' }
] results
  • The await keyword is used with Promise.all([p1, p2, p3]). This means that the code will pause here and wait for all three promises to either resolve or reject.

  • Since all the promises are set to resolve successfully, they complete their tasks after their respective delays and return objects with success messages.

  • Promise.all collects the results of all the resolved promises into an array. In this case, it's an array of three objects with success messages.

  • The console.log statement logs this array, followed by the string "results," which helps identify what is being logged.

Example 2:

Suppose you have three asynchronous functions that return promises but one of the promises gets rejected:

I'm rejecting a promise intentionally by setting the second parameter of p2 as false

async function main() {
  try {
    // Create three promises with different delays and failure conditions.
    let p1 = promiseFunction(2000, true, "one");
    let p2 = promiseFunction(5000, false, "two");
    let p3 = promiseFunction(1000, true, "three");

    let results = await Promise.all([p1, p2, p3]);

    console.log(results, "results");
  } catch (e) {
    console.log(e, "error");
  }
}

main();

Output:

{ msg: 'Failed 5000 two' } error

With p2 intentionally set to reject by passing false as the second parameter, the output shows the rejection message for "two," as expected. To handle this rejection and prevent unhandled promise rejections, it's essential to include a catch block. In the code you provided, the catch block effectively handles the error by logging it, ensuring that no unhandled promise rejection occurs.

2. Promise.allSettled()

With Promise.allSettled(), you can pass an array of promises and await their settlement. Settlement refers to either resolution or rejection. The result is an array of objects, each representing the status of a promise. These status objects typically include the status property, which can be either "fulfilled" for resolved promises or "rejected" for rejected promises, and the value or reason property, depending on whether the promise resolved or rejected.

Example 1: Promise.allSettled() - All Promises Resolved

In this example, we use Promise.allSettled() to handle a set of promises, all of which resolve successfully. The results of each promise are logged to the console.

javascriptCopy codeasync function main() {
  try {
    // Create three promises with different delays and success conditions.
    let p1 = promiseFunction(2000, true, "one");
    let p2 = promiseFunction(5000, true, "two");
    let p3 = promiseFunction(1000, true, "three");

    let results = await Promise.allSettled([p1, p2, p3]);
    // Log the result of all promises, regardless of resolution or rejection.
    console.log(results, "results");
  } catch (e) {
    console.log(e, "error");
  }
}

main();

Output:

[
  { status: 'fulfilled', value: { msg: 'Successful 2000 one' } },
  { status: 'fulfilled', value: { msg: 'Successful 5000 two' } },
  { status: 'fulfilled', value: { msg: 'Successful 1000 three' } }
] results

In this scenario, all promises (p1, p2, and p3) resolve successfully, and Promise.allSettled() returns an array of status objects for each promise. The status is marked as 'fulfilled', and the value property contains the result of each resolved promise.

Example 2: Promise.allSettled() - At Least One Promise Rejects

In this example, we utilize Promise.allSettled() with a mix of resolved and rejected promises. It demonstrates how the method provides information about each promise's outcome, even when some promises reject.

javascriptCopy codeasync function main() {
  try {
    // Create three promises with different delays and success/failure conditions.
    let p1 = promiseFunction(2000, true, "one");
    let p2 = promiseFunction(5000, false, "two"); // Intentionally set to reject
    let p3 = promiseFunction(1000, true, "three");

    let results = await Promise.allSettled([p1, p2, p3]);

    console.log(results, "results");
  } catch (e) {
    console.log(e, "error");
  }
}

main();

Output:

[
  { status: 'fulfilled', value: { msg: 'Successful 2000 one' } },
  { status: 'rejected', reason: { msg: 'Failed 5000 two' } },
  { status: 'fulfilled', value: { msg: 'Successful 1000 three' } }
] results

In this scenario, some promises resolve successfully (p1 and p3), while one promise (p2) intentionally rejects. Promise.allSettled() returns an array of status objects, indicating the status as 'fulfilled' for resolved promises and 'rejected' for the rejected promise. The value property contains the result of resolved promises, and the reason property contains the rejection reason for the rejected promise.

These examples showcase how Promise.allSettled() is a versatile tool for handling various outcomes of multiple promises, making it easier to manage and respond to asynchronous operations in your JavaScript applications.

3. Promise.race()

Asynchronous programming in JavaScript often involves multiple asynchronous tasks running concurrently, and you might want to respond as soon as the first task is completed, rather than waiting for all of them to finish. This is where Promise.race() comes into play.

Promise.race() is a method provided by JavaScript Promises that allows you to compete promises against each other and respond to the first one that either resolves or rejects. It provides a way to implement scenarios where you need to race asynchronous operations, for example, handling multiple network requests and responding as soon as the first one returns.

With Promise.race(), you pass an array of promises, and it returns a new promise. This new promise resolves or rejects as soon as any of the promises in the array resolves or rejects, whichever comes first.

Example 1: Promise.race() - Fastest Promise Resolves

In this example, we use Promise.race() to compete multiple promises, and the first one to resolve is handled.

async function main() {
  try {
    // Create three promises with different delays and success/failure conditions.
    let p1 = promiseFunction(2000, false, "one");
    let p2 = promiseFunction(5000, false, "two");
    let p3 = promiseFunction(1000, true, "three"); // This will resolve first

    let results = await Promise.race([p1, p2, p3]);
    // Log the result of the fastest resolved promise.
    console.log(results, "results");
  } catch (e) {
    // If any promise rejects, handle the error by logging it.
    console.log(e, "error");
  }
}

main();

Output:

{ msg: 'Successful 1000 three' } results

In this scenario, Promise.race() competes the three promises, and the first one to resolve is p3 in 1 second. As soon as p1 resolves, the Promise.race() resolves with its result. The outcome of p3 is logged as "results." It won't worry about other promises that are yet to be either resolved or rejected.

Example 2: Promise.race() - First Promise Rejects

In this example, we use Promise.race() with a mix of resolved and rejected promises, and it demonstrates how it handles the first promise that rejects.

async function main() {
  try {
    // Create three promises with different delays and success/failure conditions.
    let p1 = promiseFunction(2000, true, "one");
    let p2 = promiseFunction(5000, true, "two"); 
    let p3 = promiseFunction(1000, false, "three"); // Intentionally set to reject

    let results = await Promise.race([p1, p2, p3]);
    // Log the result of the fastest promise, whether resolved or rejected.
    console.log(results, "results");
  } catch (e) {
    // Handle the rejection from the first promise by logging it.
    console.log(e, "error");
  }
}

main();

Output:

{ msg: 'Failed 1000 three' } error

In this scenario, Promise.race() competes the three promises, and the first one to reject is p3, which was intentionally set to reject. As soon as p3 rejects, the Promise.race() resolves with the rejection reason. The rejection reason of p3 is logged as "error."

These examples illustrate how Promise.race() is used to handle the fastest promise to resolve or reject, providing a means to optimize response times in asynchronous operations.

4. Promise.any()

Promise.any() allows you to concurrently execute multiple promises and respond as soon as the first settled promise in the given array resolves successfully. If the first settled promise in the array gets rejected, it doesn't immediately reject the entire operation. Instead, it waits for at least one promise to resolve successfully and returns the result of that settled promise. If all promises are rejected, it aggregates the rejection reasons into an AggregateError.

You can catch this AggregateError in a catch block and access the individual rejection reasons using e.errors, where e is the caught error.

Certainly, here are the examples for Promise.any() with explanations for each scenario:

Example 1: Promise.any() - First Settled Promise Resolves

In this example, Promise.any() is used to concurrently execute three promises with different delays and success/failure conditions. The first settled promise will be resolved here.

async function main() {
  try {
    // Create three promises with different delays and failure conditions.
    let p1 = promiseFunction(2000, true, "one");
    let p2 = promiseFunction(5000, false, "two");
    let p3 = promiseFunction(1000, true, "three");

    let results = await Promise.any([p1, p2, p3]);

    console.log(results, "results");
  } catch (e) {
    console.log(e, "error");
  }
}

main();

Output:

{ msg: 'Successful 1000 three' } results

In this scenario, the first settled promise is p3, which resolves successfully. Therefore, Promise.any() resolves with the value of p3, and the result is logged as "results."

Example 2: Promise.any() - First Settled Promise Rejects

In this example, Promise.any() is used to concurrently execute three promises with different delays and success/failure conditions. The first settled promise will be rejected. In this case, the first settled promise is p3, which rejects.

async function main() {
  try {
    // Create three promises with different delays and failure conditions.
    let p1 = promiseFunction(2000, true, "one");
    let p2 = promiseFunction(5000, true, "two");
    let p3 = promiseFunction(1000, false, "three");

    let results = await Promise.any([p1, p2, p3]);

    console.log(results, "results");
  } catch (e) {
    console.log(e, "error");
  }
}

main();

Output:

{ msg: 'Successful 2000 one' } results

In this example even though the first settled promise, p3, results in rejection, Promise.any() continues to monitor the remaining promises for resolution. It gives each promise a chance to succeed and then resolves with the value of the first promise that eventually resolves, which is p1 in this case.


Example 3: Promise.any() - All Promises Reject

In this example, Promise.any() is used to concurrently execute three promises, and all of them reject. This demonstrates how to catch and handle the AggregateError that results when all promises reject.

async function main() {
  try {
    // Create three promises with different delays and all set to reject.
    let p1 = promiseFunction(2000, false, "one");
    let p2 = promiseFunction(5000, false, "two");
    let p3 = promiseFunction(1000, false, "three");

    let results = await Promise.any([p1, p2, p3]);

    console.log(results, "results");
  } catch (e) {
    console.log(e);
    console.log(e.errors);
  }
}

main();

Output:

AggregateError: All promises were rejected
[
  { msg: 'Failed 2000 one' },
  { msg: 'Failed 5000 two' },
  { msg: 'Failed 1000 three' }
]

In this scenario, since all promises reject, Promise.any() throws an AggregateError. You can catch this error in the catch block and access the individual rejection reasons using e.errors. It allows you to handle and log the rejection reasons of all the promises that rejected.

Conclusion

Promises are a crucial tool in JavaScript for handling asynchronous operations, and JavaScript offers a variety of promise-related methods to manage and coordinate them effectively. In this blog, we explored four such methods: Promise.all(), Promise.allSettled(), Promise.race(), and Promise.any().

  • Promise.all() allows you to execute multiple promises concurrently and wait for all of them to settle. This method is ideal when you need all promises to succeed before proceeding. It is especially useful for scenarios where you want to make multiple asynchronous requests and ensure they all complete successfully. However, keep in mind that if any promise is rejected, the entire operation is rejected, which makes it a good choice when all promises are equally important.

  • Promise.allSettled() is an excellent choice when you want to know the outcome of every promise in a set, whether it resolved successfully or was rejected. It returns an array of status objects, allowing you to inspect the results and reasons for both resolved and rejected promises. This method is valuable for cases where you want to perform cleanup or logging regardless of the promises' outcome.

  • Promise.race() is designed for scenarios where you need to respond as soon as the first promise settles, whether it resolves or rejects. It allows you to optimize response times, for example, when making multiple network requests and wanting to respond as soon as the first one returns. In cases where time is of the essence, it's a powerful tool.

  • Promise.any() is the latest addition to the promise methods and is particularly useful when you want to respond as soon as the first promise in an array settles successfully. If the first settled promise is a rejection, it waits for the next one to resolve successfully. If all promises get rejected, it aggregates the rejection reasons into an AggregateError, which you can catch and handle. This method is an excellent choice when you want to optimize responsiveness and gracefully handle scenarios where not all promises are expected to succeed.

Understanding these promise methods and when to use them is vital for writing efficient, responsive, and maintainable asynchronous JavaScript code. Depending on your specific use case, you can choose the most appropriate method to manage your promises effectively, making your applications more robust and user-friendly.