In modern web development, fetching data from APIs is a common task. Often, applications need to fetch data from multiple sources simultaneously to provide a seamless user experience. Synchronous API calls can lead to performance bottlenecks, as the application has to wait for each request to complete before initiating the next one. This is where concurrent API calls come into play, allowing the application to fetch data from multiple sources in parallel, significantly improving performance and responsiveness.
Concurrency Scenario
Imagine building a travel booking application that needs to gather information from various APIs, like flight schedules, hotel availability, and rental car options. Fetching this data sequentially leads to a slow user experience. Concurrent API calls allow you to retrieve everything at once. Additionally, you might want fine-grained control over how many requests are sent at a time.
Calling APIs Concurrently with p-map
The p-map library is a handy tool that executes asynchronous operations concurrently with a configurable limit on the number of simultaneous processes. This is beneficial when making multiple API calls, as it lets you manage how many requests happen at once, preventing you from overwhelming servers or exceeding rate limits.
To control concurrency with p-map, simply provide a concurrency
option:
import pMap from 'p-map';
async () => {
// an array of promises
const promise = [
// array of promiese please refer to the replit link
];
// the function that handles each promise
const processPromise = async (promise) => {
const value = await promise;
return value;
};
const result = await pMap(promise, processPromises, {concurrency: 2})
}
Importantly, p-map preserves the order of results even when promises resolve at different times, ensuring the output aligns with your original input.
Considerations and Error Handling
It's important to understand why controlling concurrency matters. Here are key reasons:
- Client-side resource management: Avoid consuming too much memory with large requests.
- Preventing server overload: Don't overwhelm servers with too many simultaneous requests.
- Error handling: p-map provides an option (
stopOnError
) to stop on the first error or continue through all promises. When an error occurs, anAggregateError
is thrown, giving you visibility into the issue.
In this example, we set stopOnError: false and create a promise with error.
Even if there is an error, p-map still handles other promises and throw an AggregateError at the end. If we loop the AggregateError, we can see that it is an array withe the thrown error like this:
["promise is rejected"]
Calling APIs Concurrently with RxJS mergeMap
RxJS is a library for composing asynchronous programs using observable sequences. It comes with operators like mergeMap
that provides powerful ways to handle concurrency. When dealing with multiple http requests rxjs has excellent solutions using other operators like switchMap/concatMap/exhaustMap. If you are interested to learn more the differences among these operators and their use cases please refer to my other article: https://medium.com/dev-genius/learn-rxjs-in-an-easy-way-8008f291ef8f.
A typical code example for mergeMap is like this:
import {from} from "rxjs";
import { mergeMap, catchError } from "rxjs/operators";
// promises is an array of promises
from(promises).pipe(
mergeMap(
(promise) => from(promisePromise(promise)),
// the second parameter is concurrent requests
2
),
);
In the console log, it is clear that mergeMap process all promises even if there is an error in the original promises.
The difference between p-map and mergeMap is that:
- the output of mergeMap is a value each time instead of an array like pMap.
- the order of the output is not the same as that of the input.
Error Handling:
If there is an error in the promises, when it is subscribed the error function could catch the error and log it (the screenshot is the subscribe method).
fetchAllDataWithMergeMap.subscribe({
//next: (value) => console.log("next", value),
error: (error) => console.log("onError=>", error),
// complete: () => console.log("complete"),
});
Just like p-map or Promise.allSettled(), mergeMap could also handle the rest promises even if there is an error in the promises.
Conclusion
While both Promise.all
and Promise.allSettled
run promises concurrently, they differ in error handling and result management. Promise.all
fails immediately if any promise rejects, while Promise.allSettled
provides results for all promises (fulfilled or rejected). For more flexible concurrency control, consider p-map
or RxJS's mergeMap
. Both allow you to limit simultaneous requests and handle errors gracefully. p-map
maintains the original input order of results, whereas mergeMap
emits values as they resolve. To customize error behavior, p-map
uses a stopOnError
parameter, and RxJS leverages error functions within subscriptions.
Feel free to playground with the code. Here is the link in replit: https://replit.com/join/upindkctis-mingwu1
In Plain English 🚀
Thank you for being a part of the In Plain English community! Before you go:
- Be sure to clap and follow the writer ️👏️️
- Follow us: X | LinkedIn | YouTube | Discord | Newsletter
- Visit our other platforms: Stackademic | CoFeed | Venture | Cubed
- More content at PlainEnglish.io