Sling Academy
Home/JavaScript/Orchestrating Multiple Async Tasks Without resorting to switch or try/catch in JavaScript

Orchestrating Multiple Async Tasks Without resorting to switch or try/catch in JavaScript

Last updated: December 12, 2024

Asynchronous programming is a powerful feature of JavaScript that allows developers to perform non-blocking operations. When utilizing asynchronous programming, you often need to handle multiple tasks, coordinate their execution, and manage possible errors in a structured way.

Developers typically use methods like try/catch to handle errors or switch statements to control the flow. However, JavaScript offers built-in mechanisms that can help orchestrate multiple async tasks more gracefully without resorting to these traditional approaches. In this article, we will explore techniques like Promise.all, Promise.race, async/await, and custom iterators to manage async tasks.

1. Utilizing Promise.all for Parallel Execution

Promise.all is a method that takes an array of promises and returns a single promise that resolves when all of the promises in the array have resolved or when any one of them rejects. It allows you to perform multiple tasks in parallel and wait for all tasks to complete.

const fetchData = async () => {
    try {
        const [data1, data2, data3] = await Promise.all([
            fetch('/api/data1'),
            fetch('/api/data2'),
            fetch('/api/data3'),
        ]);

        const json1 = await data1.json();
        const json2 = await data2.json();
        const json3 = await data3.json();

        console.log(json1, json2, json3);
    } catch (error) {
        console.error('Error fetching data:', error);
    }
};

fetchData();

This example shows how to fetch data from multiple APIs concurrently. The key is in the use of Promise.all, which makes sure all requests are completed before proceeding.

2. Running the First Completed Task with Promise.race

When you want to execute multiple promises and use the result of the first one that resolves, Promise.race is your friend. This method returns a promise that resolves or rejects as soon as one of the promises settles, regardless of the outcome (fulfilled or rejected).

const fetchFirst = async () => {
    try {
        const result = await Promise.race([
            fetch('/api/first'),
            new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 5000)), // timeout after 5 seconds
        ]);

        const json = await result.json();
        console.log('First response:', json);
    } catch (error) {
        console.error('Error:', error);
    }
};

fetchFirst();

Here, we're fetching from an endpoint and imposing a timeout condition. The second promise will cause Promise.race to reject if the fetch operation takes longer than expected, effectively handing timeout scenarios gracefully.

3. Sequential Execution with async/await

Sometimes, tasks need to run in a specific order. Using async/await syntax, we can ensure that each task is completed before the next one starts.

async function sequentialTasks() {
    try {
        const response1 = await fetch('/api/step1');
        const result1 = await response1.json();
        console.log('Step 1 complete:', result1);

        const response2 = await fetch(`/api/step2?id=${result1.id}`);
        const result2 = await response2.json();
        console.log('Step 2 complete:', result2);

        const response3 = await fetch(`/api/step3?id=${result2.id}`);
        const result3 = await response3.json();
        console.log('Step 3 complete:', result3);
    } catch (error) {
        console.error('Error in sequence:', error);
    }
}

sequentialTasks();

This pattern is not only easy to read but also effectively handles each step in an asynchronous workflow without needing complex control structures like switch.

4. Error Handling with Fallback Mechanisms

Instead of relying on try/catch everywhere, you can use a fallback structure when handling promises. This involves chaining .catch() to each promise to ensure error handling is specific to each operation.

fetch('/api/data')
    .then(response => response.json())
    .then(json => console.log('Data:', json))
    .catch(error => console.error('Failed to fetch data:', error));

By organizing asynchronous operations as depicted, JavaScript lets you manage concurrent processes effectively and flexibly.

Conclusion

Handling multiple asynchronous tasks efficiently is essential for modern web development. By leveraging tools like Promise.all, Promise.race, and async/await, you avoid overly complex control structures, resulting in cleaner, more maintainable code. As an exercise, try implementing these techniques in your own projects, observe the execution differences, and adopt the approaches that best fit your use case.

Next Article: Constructing Conditional Execution Tables for Easier Maintenance in JavaScript

Previous Article: Using Optional Chaining to Avoid Errors and Enhance Control Flow in JavaScript

Series: Mastering Control Flow in JavaScript

JavaScript

You May Also Like

  • Handle Zoom and Scroll with the Visual Viewport API in JavaScript
  • Improve Security Posture Using JavaScript Trusted Types
  • Allow Seamless Device Switching Using JavaScript Remote Playback
  • Update Content Proactively with the JavaScript Push API
  • Simplify Tooltip and Dropdown Creation via JavaScript Popover API
  • Improve User Experience Through Performance Metrics in JavaScript
  • Coordinate Workers Using Channel Messaging in JavaScript
  • Exchange Data Between Iframes Using Channel Messaging in JavaScript
  • Manipulating Time Zones in JavaScript Without Libraries
  • Solving Simple Algebraic Equations Using JavaScript Math Functions
  • Emulating Traditional OOP Constructs with JavaScript Classes
  • Smoothing Out User Flows: Focus Management Techniques in JavaScript
  • Creating Dynamic Timers and Counters with JavaScript
  • Implement Old-School Data Fetching Using JavaScript XMLHttpRequest
  • Load Dynamic Content Without Reloading via XMLHttpRequest in JavaScript
  • Manage Error Handling and Timeouts Using XMLHttpRequest in JavaScript
  • Handle XML and JSON Responses via JavaScript XMLHttpRequest
  • Make AJAX Requests with XMLHttpRequest in JavaScript
  • Customize Subtitle Styling Using JavaScript WebVTT Integration