JS Parallel Promises: Boost Your Code’s Efficiency

JS parallel promises sceenshot

I recently came across a fantastic article by Jules Blom that explores the fascinating realm of JS parallel promises. It’s all about unlocking the power of running JavaScript promises in parallel. And you know what? I couldn’t resist the opportunity to share this newfound knowledge with you! Together, we’ll unravel the mysteries and unleash the potential of JS parallel promises. Are you ready? Let’s dive in!

Promises come in three distinct states: pending, fulfilled, and rejected.

  1. When a promise is pending, it means the magic is happening behind the scenes.
  2. Once fulfilled, the promise triumphantly delivers a value,
  3. while a rejected promise signals a detour due to an error.

When a promise is no longer in the pending state and has either been fulfilled or rejected, we consider it settled. Once a promise is settled, it cannot go back to being pending. It’s like reaching a point of no return!

When it comes to working with promises, async and await are like best buddies that make our lives easier. By adding the magic keyword “async” to a function, we unlock a whole new level of flexibility and simplicity in handling promises. When adding async:

  1. The function always returns a promise
    • The return value becomes the Promise fulfillment value
    • Exceptions are turned into rejected Promises
  2. It’s also possible to use await within the function

When it comes to handling promises in JavaScript, the await operator is a game-changer. By gracefully suspending the function and waiting for the promise to settle, it unlocks the power of using the fulfillment value as a regular return value. 🚀

The beauty of async and await lies in their ability to transform your code into a familiar, linear-in-time format that flows naturally. No more tangled callbacks or complex asynchronous code! With async and await, you can write code that looks and feels like the traditional sequential code we all know and love. It’s a breath of fresh air in the world of JavaScript parallel promises.

Don’t Fall into the Async Trap: Avoiding a Common Pitfall

Let’s talk about a common mistake that often catches us off guard when working with async functions. The thing is, async functions may look deceptively similar to regular, linear-in-time code, leading us to forget their true nature. The reality is that async functions operate outside of the normal execution order.

async function fetchInventory() {
  const cars = await fetch("api/cars");
  const trucks = await fetch("api/trucks");
  const boats = await fetch("api/boats");
  const planes = await fetch("api/planes");
  const trains = await fetch("api/trains");

 return { cars, trucks, boats, planes, trains };
}

I’m aware that there’s more to fetching than what’s covered here, such as error handling and response.ok checks, but let’s stay focused on the main topic of this post.

One particular pitfall that many developers stumble upon is awaiting multiple promises sequentially. While it might seem intuitive to wait for each promise to settle before moving on to the next one, this approach actually blocks the execution, causing our code to run noticeably slower. 😱

But fear not! There’s a smarter way to handle this situation. By utilizing the power of parallel promises, we can leverage the full potential of asynchronous operations and significantly improve the efficiency of our code. 🚀 So, let’s explore the world of JS parallel promises and discover how we can avoid this common mistake, boost our code’s performance, and unlock new levels of productivity! 💪

Parallel Execution with JS Parallel Promises

When working with independent promises (meaning the async task does not depend on the results of previous async tasks), we can optimize their execution by running them in parallel.

JavaScript provides four static methods (a static method is callable directly from the Promise object and not from an instantiated promise) in the Promise object specifically designed for this purpose.

These methods, including .all, .race, .allSettled, and .any, accept an iterable (typically an array) of promises, and return a promise. Each method has distinct characteristics, such as failing fast or not failing fast, and returning a single fulfillment value or multiple fulfillment values.

Returns allReturns single
Fails fast.all.race
Does not fail fast.allSettled.any

With this knowledge, we can choose the appropriate method based on our specific requirements. Let’s explore the different options available for parallel promise execution.

Parallel Execution with Promise.all

When we have a set of promises that need to fulfill before proceeding, Promise.all comes to the rescue. It returns a promise that resolves with an array of fulfillment values once all the passed promises are fulfilled. However, if any of the promises reject, Promise.all immediately rejects with the reason of that particular rejection. This behavior is known as “failing fast,” where the promise chain is aborted as soon as an error occurs.

The Promise.all() static method takes an iterable of promises as input and returns a single Promise. This returned promise fulfills when all of the input’s promises fulfill (including when an empty iterable is passed), with an array of the fulfillment values. It rejects when any of the input’s promises rejects, with this first rejection reason.

MDN: Promise.all()

Promise.all Use Case

In order to ensure that all necessary data is available for rendering the page, we can adapt the previous code to run promises in parallel. If any of the requests fail, it is crucial to handle the error and display an appropriate error message:

const endpoints = [ "cars", "trucks", "boats", "planes", "trains" ];

async function fetchInventory() {
  const fetchCalls = enpoints.map((endpoint) => fetch(`api/${endpoint}`));

  const results = Promise.all(fetchCalls);

  return results;
}

Promise.allSettled: Gathering Results Without Failing Fast

In certain scenarios, obtaining the outcome of all promises, regardless of whether they fulfill or reject, is desired. This is where Promise.allSettled comes into play.

Promise.allSettled returns an array of objects that provide information about the status of each promise once all promises have settled. Unlike Promise.all, Promise.allSettled never returns a rejected promise; it always returns a fulfilled promise upon completion.

Return value: Asynchronously fulfilled, when all promises in the given iterable have settled (either fulfilled or rejected). The fulfillment value is an array of objects, each describing the outcome of one promise in the iterable, in the order of the promises passed, regardless of completion order.

MDN: Promise.allSettled()

Promise.allSettled Use Case

In a scenario similar to Promise.all, let’s consider a situation where not all data is necessary to display a page. Instead, each result is shown in its own panel. If a fetch operation fails, an error message is displayed in the corresponding panel while the remaining panels continue to display their results as usual:

const endpoints = [ "cars", "trucks", "boats", "planes", "trains" ];

async function fetchInventory() {
  const fetchCalls = enpoints.map((endpoint) => fetch(`api/${endpoint}`));

  const results = Promise.allSettled(fetchCalls);

  return results;
}

Achieve Fast Results with JS Parallel Promises: Promise.race

When working with parallel promises in JavaScript, the Promise.race method offers a valuable tool to optimize performance and responsiveness. Similar to Promise.all, Promise.race allows you to work with multiple promises simultaneously. However, it has a distinct characteristic: it returns the result of the first promise that settles, whether it’s a fulfillment or rejection.

Imagine a scenario where you have multiple promises representing different asynchronous operations. You need to retrieve the fastest result possible, as it holds critical information for your application. This is where Promise.race comes into play. By leveraging this method, you can initiate all the promises concurrently and obtain the outcome of the one that settles first.

For example, let’s say you have multiple fetch requests to different APIs, and you want to display the data from the fastest response on your page. Using Promise.race, you can kick off all the fetch requests simultaneously and retrieve the data from the first request to resolve, regardless of whether it’s a success or failure. This allows you to display relevant information to your users promptly.

It’s important to note that Promise.race follows the fail-fast principle. As soon as one of the promises in the race settles, the resulting promise will also settle. This behavior ensures that you receive the earliest result, enhancing the efficiency of your application.

The next bit we need is Promise.race, the little brother of the famous Promise.all that is useful for about one real world thing, so no one really cares about it. Quoting MDN, Promise.race() returns a promise that fulfills or rejects as soon as one of the promises in an iterable fulfills or rejects, with the value or reason from that promise. That’s exactly what we need!Thoughspile.tech — How to timeout a promise

Thoughspile.tech — How to timeout a promise

Promise.race(iterable) takes an iterable over Promises (thenables and other values are converted to Promises via Promise.resolve()) and returns a Promise. The first of the input Promises that is settled passes its settlement on to the output Promise. If [the] iterable is empty then the Promise returned by race() is never settled. As an example, let’s use Promise.race() to implement a timeout. 

Exploring JS — 25.11.4 Timing out via Promise.race()

The Promise.race() method is best used in situations where you want to be able to short-circuit the completion of a number of different promises. Unlike Promise.any(), where you specifically want one of the promises to succeed and only care if all promises fail, with Promise.race() you want to know even if one promise fails as long as it fails before any other promise fulfills. Here are some situations where you may want to use Promise.race().

Understanding JavaScript Promises Chapter 3: Working with Multiple Promises – The Promise.race() Method – When to Use Promise.race()

Promise.race Use Case

One powerful and legitimate use case of Promise.race in the realm of JS parallel promises is to enforce a timeout on a lengthy asynchronous task. By utilizing Promise.race, you can race the long-running task against a classic ‘sleep‘ Promise, effectively setting a time limit for its execution. This allows you to efficiently manage the duration of asynchronous operations, preventing delays and ensuring optimal performance.

const timeout = new Promise((_resolve, reject) => {
  setTimeout(() => {
    const duration = 5000;
    reject(`Timed out after ${duration / 1000} sec`);
  }, 5000);
});

const readFromCacheWithTimeout = Promise.race([readFromCache, timeout]);

Harnessing the Power of Promise.any for Fast and Flexible Parallel Promises

Promise.any is a valuable tool in the realm of JS parallel promises. It serves as the more lenient counterpart to Promise.race, as it fulfills on the first fulfilled Promise, disregarding any rejections. The only scenario in which Promise.any can result in rejection is when all passed promises are rejected. In such cases, it returns a Promise rejected with a distinctive AggregateError, which includes an errors property listing all the rejection values.

Promise.any Use Case

The use cases for Promise.any are diverse and practical. One common scenario is when you need to run multiple asynchronous tasks for the same data and want to utilize the response from the fastest task. For example, when fetching data from various sources like a cache and a server, Promise.any allows you to use the result from the quickest response.

const data = Promise.any([readDataFromCache(), fetchData()]);

Another useful application of Promise.any is when you want to determine if you receive a successful response from any of multiple calls. This can be helpful when checking the availability of a service by receiving successful responses from different health endpoints. Promise.any empowers you to handle such cases efficiently and effectively.

const checkServiceOnline = Promise.any([
  fetch("https://api.example.com/inventory");
  fetch("https://api-backup.example.com/inventory");
  fetch("https://api-backup-2.example.com/inventory");
]);

Wrapping Up

Level up your JavaScript code by leveraging the power of parallel promises. Take advantage of the four Promise combinator methods to optimize performance and avoid common pitfalls. Don’t forget to enable ESLint’s no-await-in-loop rule to ensure cleaner and more efficient code. Embrace the world of JS parallel promises for faster and smarter asynchronous operations.

Share Your Thoughts

Your email address will not be published. Required fields are marked *

Latest Articles