Payload Logo

Parallelizing Requests in JavaScript

Author

Nick Magidson

Date Published

Fictional galaxy

Introduction

When building modern web applications, it's common to need data from multiple sources. For example, you might need user information, posts, and comments.

If you fetch these one at a time, each request waits for the previous one to finish. That can slow things down, especially if one request takes longer than the others.

A more efficient approach is to run requests in parallel using Promise.all(). This can improve performance and provide a smoother user experience.


Let's use Promise.all()

Promise.all() is a method that accepts an iterable (usually an array) of promises and returns a single promise that resolves when all the input promises have resolved.

If any promise rejects, Promise.all() immediately rejects with the reason from the first promise that failed. This makes it powerful for parallelizing independent asynchronous operations, but also something to handle carefully.

How it Works:

1const urls = [
2 'https://api.example.com/users',
3 'https://api.example.com/posts',
4 'https://api.example.com/comments'
5];
6
7async function fetchAll() {
8 try {
9 const [users, posts, comments] = await Promise.all(
10 urls.map(url =>
11 fetch(url)
12 .then(res => {
13 if (!res.ok) throw new Error(`HTTP error! ${res.status}`);
14 return res.json();
15 })
16 )
17 );
18
19 console.log({ users, posts, comments });
20 } catch (error) {
21 console.error('Failed to fetch data:', error);
22 }
23}

In this example, fetchAll() initiates all three fetch requests simultaneously. Promise.all() waits for all of them to resolve and then processes the responses.

This approach can significantly reduce the total time spent waiting for all requests to complete compared to handling them sequentially.

Important: This approach works best when your requests are independent of each other, meaning one request doesn't need the result of another to proceed. If your requests have dependencies (like needing a user ID from the first request to fetch that user's posts), you'll need to handle them sequentially or in stages.


Handling Errors Gracefully

If any promise in Promise.all() rejects, the entire operation fails. To handle each result independently, you can use Promise.allSettled():

1const results = await Promise.allSettled(
2 urls.map(url => fetch(url).then(res => res.json()))
3);
4
5results.forEach((result, index) => {
6 if (result.status === 'fulfilled') {
7 console.log(`Request ${index} succeeded:`, result.value);
8 } else {
9 console.warn(`Request ${index} failed:`, result.reason);
10 }
11});

Now you can see which requests succeeded and which failed, without stopping everything.


Conclusion

  • Use Promise.all() for independent requests that can run at the same time.
  • Check for HTTP errors; fetch won’t throw for things like 404.
  • Use Promise.allSettled() if you need all results, regardless of failures.

Parallelizing requests with Promise.all() can make your web applications faster and more responsive. By understanding how it handles errors and when it’s appropriate to use, you can write more efficient asynchronous code without introducing subtle bugs.