Handling Parallel Requests and Partial Failures in Axios

When building modern web applications, executing multiple HTTP requests concurrently can significantly improve performance. However, standard methods like Promise.all() terminate early if even a single request fails, dropping the data from successful calls. This article explains how to execute parallel requests using Axios and handle partial failures gracefully using Promise.allSettled() and inline error-catching strategies.


The Limitation of Promise.all()

The standard way to run asynchronous operations in parallel in JavaScript is Promise.all(). While efficient, Promise.all() has a "fail-fast" mechanism:

// If any request fails, the entire block jumps to the catch handler
try {
  const [users, posts] = await Promise.all([
    axios.get('/api/users'),
    axios.get('/api/posts')
  ]);
} catch (error) {
  // You lose access to the successful request's data
  console.error("One or more requests failed", error);
}

If /api/posts returns a 500 error, the successful response from /api/users is lost within the catch block.


ES2020 introduced Promise.allSettled(), which waits for all promises to finish regardless of whether they resolve or reject. It returns an array of objects describing the outcome of each promise.

Implementation

import axios from 'axios';

async function fetchDashboardData() {
  const userPromise = axios.get('https://api.example.com/users');
  const postPromise = axios.get('https://api.example.com/posts');
  const notificationPromise = axios.get('https://api.example.com/notifications');

  const results = await Promise.allSettled([
    userPromise,
    postPromise,
    notificationPromise
  ]);

  const [usersResult, postsResult, notificationsResult] = results;

  // Process Users
  if (usersResult.status === 'fulfilled') {
    console.log('Users Data:', usersResult.value.data);
  } else {
    console.error('Users request failed:', usersResult.reason.message);
  }

  // Process Posts
  if (postsResult.status === 'fulfilled') {
    console.log('Posts Data:', postsResult.value.data);
  } else {
    console.error('Posts request failed:', postsResult.reason.message);
  }

  // Process Notifications
  if (notificationsResult.status === 'fulfilled') {
    console.log('Notifications Data:', notificationsResult.value.data);
  } else {
    console.error('Notifications request failed:', notificationsResult.reason.message);
  }
}

Each result object contains:


Method 2: Inline Catch Handlers with Promise.all()

If you are working in an environment that lacks Promise.allSettled() support, you can attach an individual .catch() handler to each Axios call inside a Promise.all() array. This prevents errors from bubbling up and rejecting the parent promise.

Implementation

import axios from 'axios';

async function fetchAnalytics() {
  const [metrics, logs] = await Promise.all([
    axios.get('/api/metrics').catch((err) => ({ error: true, details: err })),
    axios.get('/api/logs').catch((err) => ({ error: true, details: err }))
  ]);

  if (metrics.error) {
    console.warn('Metrics failed to load:', metrics.details.message);
  } else {
    console.log('Metrics:', metrics.data);
  }

  if (logs.error) {
    console.warn('Logs failed to load:', logs.details.message);
  } else {
    console.log('Logs:', logs.data);
  }
}

By returning a fallback value or an error wrapper object in the .catch() block, the promise resolves successfully, allowing Promise.all() to complete.


Summary Checklist

  1. Use Promise.allSettled() as the primary solution for parallel calls where each request's state needs independent evaluation.
  2. Inspect the status property (fulfilled vs. rejected) before attempting to access result.value.data.
  3. Use inline .catch() blocks if you prefer defining default fallback data directly inside the request declaration.