Explain Codes LogoExplain Codes Logo

Call async/await functions in parallel

javascript
async-programming
promise-handling
parallel-execution
Nikita BarsukovbyNikita Barsukov·Sep 16, 2024
TLDR

To execute async/await functions in parallel, we make use of Promise.all. It handles an array of promises and resolves when all of them are resolved. Here's how you do it:

async function fetchData() { /* ... */ } async function fetchMoreData() { /* ... */ } const [data, moreData] = await Promise.all([fetchData(), fetchMoreData()]);

Upon completing their execution concurrently, the variables data and moreData will hold the results of the functions

Handling errors and understanding alternatives

Error handling is made more flexible with individual try...catch blocks than with Promise.all, which rejects immediately if any promise rejects. To get a detailed description of failures, you may want to use Promise.allSettled. Be aware though, this is a relatively new method and won't work with Internet Explorer. Here's an alternative using try...catch:

async function processInParallel() { try { const [data, moreData] = await Promise.all([fetchData(), fetchMoreData()]); // Have your way with the results } catch (error) { // Handle any of those pesky rejections } }

Bear in mind, handling errors for each promise and using Promise.allSettled can be a game-changer when an early stop isn't an option:

async function fetchAllData() { const results = await Promise.allSettled([fetchData(), fetchMoreData()]); for (const result of results) { if (result.status === 'fulfilled') { // Yay! Everything went according to plan } else { // Uh oh, something went south! Handle the error } } }

Performance metrics and rollback strategy

You're likely to notice a significant performance improvement when using parallel execution. You can measure this using console.time and console.timeEnd:

console.time('fetching data in parallel'); const [data, moreData] = await Promise.all([fetchData(), fetchMoreData()]); console.timeEnd('fetching data in parallel'); // Spits out the time taken

In case of using Promise.all, there's no stopping once the wheel gets rolling. If one operation relies on the result of another and might need a rollback in case of failure, you'll have to manage that manually.

Unleash the power of parallel execution

Parallel execution is not just about API calls. Sometimes, you'd want to parallelize file operations or server requests. For Node.js developers, the async library packs some punch for parallel operations, with utilities like eachLimit.

Conditional logic or dependent tasks may require unique Promise instances for each async function:

const timerPromise = (ms, value) => new Promise(resolve => setTimeout(() => resolve(value), ms)); const runConcurrently = async () => { const promises = [ timerPromise(500, 'First'), // It's the slowest, but no one' going to wait for it. timerPromise(1000, 'Second'), timerPromise(1500, 'Third') // "I came, I saw, I... finished last. But they did wait for me!" ]; return await Promise.all(promises); } runConcurrently().then(console.log); // Logs ["First", "Second", "Third"] after ~1500ms

Sequential vs parallel: The showdown

Discerning between sequential and parallel execution is vital. async/await in a loop gives sequential execution, whereas Promise.all provides parallel execution. Compare the below:

Sequential (Waiting in line):

for (const asyncFunc of [fetchData, fetchMoreData]) { await asyncFunc(); // "After you... No, I insist, after you..." }

Parallel (Rush hour):

await Promise.all([fetchData(), fetchMoreData()]); // "Get outta my way!"

Make friends with failures

Be friendly with promise rejections when using Promise.all. If one promise rejects, all fall down. Promise.allSettled may come in handy when you want all promises to settle down before moving forward.