Efficiently Handle Multiple Async Calls with Async/Await

Published on | Reading time: 5 min | Author: Andrés Reyes Galgani

Efficiently Handle Multiple Async Calls with Async/Await
Photo courtesy of Umberto

Table of Contents

  1. Introduction
  2. Problem Explanation
  3. Solution with Code Snippet
  4. Practical Application
  5. Potential Drawbacks and Considerations
  6. Conclusion
  7. Final Thoughts
  8. Further Reading

Introduction 🌟

As developers, we often find ourselves tangled in the complexities of asynchronous programming. Have you ever stared at a wall of Promises, await keywords, and then() methods, wondering if there’s a cleaner way to manage the chaos? If you’re nodding your head in agreement, you’re not alone! The process of handling asynchronous code can feel like riding a roller coaster—thrilling yet disorienting.

In the rapidly expanding JavaScript ecosystem, tools and patterns are continually emerging to enhance our coding experience. But amidst all these changes, it’s easy to overlook gems that can simplify your workflow. One such underappreciated technique involves collecting results from multiple asynchronous calls efficiently.

Let’s unravel a solution that transforms the way we handle asynchronous operations, particularly when dealing with multiple API calls. This approach not only simplifies your code but also increases its readability and maintainability—who wouldn’t want that?


Problem Explanation ☁️

Consider the following scenario: You need to fetch data from three different APIs for a dashboard. Each API returns data simultaneously, but your current method utilizes Promise.all() within nested then() calls. This leads to a significant increase in boilerplate code and can make debugging a challenge. Here’s how a conventional approach might look:

fetch('https://api1.example.com/data')
  .then(response => response.json())
  .then(data1 => {
    fetch('https://api2.example.com/data')
      .then(response => response.json())
      .then(data2 => {
        fetch('https://api3.example.com/data')
          .then(response => response.json())
          .then(data3 => {
            // Process all the data
          });
      });
  });

This nesting quickly becomes cumbersome and hard to read. You end up with "callback hell," making it challenging to manage error handling and logic flows effectively. As your project grows, maintaining this code could prove to be daunting.

Moreover, if one of the fetch calls fails, the entire sequence breaks down. The two successful requests will have done their work for nothing, leading to frustrating overhead when debugging your app.


Solution with Code Snippet 🎉

Introducing async/await with Promise.allSettled(), a powerful way to handle multiple promises cleanly and concisely. This technique not only makes your code easier to write but also provides a structured way to deal with the results of your asynchronous calls—even if some of those calls fail. Here’s how you can implement it:

async function fetchData() {
  const requests = [
    fetch('https://api1.example.com/data'),
    fetch('https://api2.example.com/data'),
    fetch('https://api3.example.com/data'),
  ];

  try {
    const responses = await Promise.allSettled(requests);

    const results = responses.map((response, index) => {
      if (response.status === 'fulfilled') {
        return response.value.json(); // Successful response
      } else {
        console.error(`Error fetching API ${index + 1}:`, response.reason);
        return null; // Return null for failed responses
      }
    });

    // Wait for all JSON data to resolve
    const data = await Promise.all(results);
    
    // Process data[0], data[1], data[2] here
  } catch (error) {
    console.error("An error occurred:", error);
  }
}

Explanation of the Code

  1. Requests Array: We create an array of fetch requests targeting three APIs.
  2. Promise.allSettled: Instead of Promise.all(), we utilize Promise.allSettled(), ensuring we get the results of all the promises—regardless of whether they resolve or reject.
  3. Mapping Results: We map through the responses to check their status. If fulfilled, we call .json(); if rejected, we log an error and return null.
  4. Process Parsed Data: Finally, we wait for all JSON data through a second Promise.all, allowing us to handle successful and failed calls effectively.

This method results in a clear flow of logic and encourages a single point of error handling while preserving the readability often sacrificed in rigid nested structure.


Practical Application 🌐

This approach is especially beneficial in scenarios where you expect multiple independent API responses, such as when aggregating user data, fetching content from various services, or even populating a data visualisation that necessitates data from multiple sources.

Consider integrating this method into:

  • Dashboards: Where data might come from various metrics.
  • Hybrid applications: Aggregating external library functionalities seamlessly.
  • Microservices: Fetching user-related data from separate microservices to show consolidated information.

Adopting this efficient structure can significantly enhance the user experience by minimizing wait times and resource load.


Potential Drawbacks and Considerations ⚖️

While this method has numerous advantages, it's worth noting that:

  1. Error Handling Complexity: If multiple APIs fail, you may end up with a lot of null values, which can complicate the logic processing your results. Be sure to create additional checks or fallback functionalities.
  2. Dependency on API Reliability: If APIs are unreliable, the system could become unusable. An additional layer of retry logic or fallback mechanisms might be beneficial here.

By factoring in retries or default data, you can mitigate the impact of third-party failures, leading to more resilient applications.


Conclusion 📝

In the evolving realm of JavaScript, leveraging async/await with Promise.allSettled() can drastically simplify your asynchronous call handling. It enhances code readability, maintainability, and resilience against failed operations, mitigating the famous callback hell.

By choosing this streamlined approach, you equip yourself with the ability to tackle complex web applications with greater confidence and less stress, paving the way for scalable and efficient codebases.


Final Thoughts 🌈

I encourage you to experiment with this technique in your projects and share your experiences. Are there other asynchronous patterns you've found effective? Let's build this knowledge community together! Drop your comments below!

Don’t forget to subscribe for more expert insights into optimizing your code and workflows. Happy coding! 🚀


Further Reading

  1. JavaScript Promises: An Introduction
  2. Managing Asynchronous JavaScript using Async/Await
  3. Promises in JavaScript: A Practical Guide

Focus Keyword: JavaScript Async/Await
Related Keywords: Promise.all, API Error Handling, Asynchronous Programming Techniques, JavaScript Fetch, API Data Fetching