jsmanifest logojsmanifest

Promise.allSettled vs Promise.all: Which One to Use?

Promise.allSettled vs Promise.all: Which One to Use?

Discover the critical differences between Promise.allSettled and Promise.all, and learn when to use each method for better async code in JavaScript.

While I was looking over some production code the other day, I came across a bug that had been causing silent failures in our analytics dashboard. The culprit? A misuse of Promise.all that was crashing the entire operation when just one API call failed. This got me thinking about how many developers—myself included—have been guilty of reaching for Promise.all without considering whether it's actually the right tool for the job.

Understanding Promise Concurrency Methods

When you're dealing with multiple asynchronous operations in JavaScript, you need a way to manage them efficiently. I was once guilty of creating nested promise chains or using sequential await statements for operations that could run in parallel. Little did I know that JavaScript provides built-in methods specifically designed for concurrent promise execution.

The two most commonly confused methods are Promise.all and Promise.allSettled. They both handle multiple promises concurrently, but their behavior when things go wrong is fundamentally different. Understanding this distinction has saved me countless debugging hours and prevented numerous production issues.

How Promise.all Works: All or Nothing

Promise.all follows an "all or nothing" approach. When you pass it an array of promises, it waits for all of them to resolve successfully. The moment any single promise rejects, the entire operation fails immediately—it doesn't wait for the other promises to finish.

Here's what makes Promise.all both powerful and dangerous: it short-circuits on the first rejection. If you have ten promises and the second one fails, Promise.all rejects immediately with that error. The remaining eight promises might still be running in the background, but you won't get their results.

The return value is an array of resolved values in the same order as the input promises. This ordering guarantee is wonderful because it means you don't need to track which result corresponds to which promise. However, this only works when everything succeeds.

Promise.all behavior diagram

How Promise.allSettled Works: Wait for Everything

Promise.allSettled takes a more patient approach. When I finally decided to use this method in production, I realized how much more resilient my code became. It waits for every single promise to complete—whether they resolve or reject—before returning.

The key difference is that Promise.allSettled never rejects. It always resolves with an array of objects describing the outcome of each promise. Each object has a status property that's either "fulfilled" or "rejected", along with either a value (for fulfilled promises) or a reason (for rejected ones).

This pattern is fascinating because it shifts error handling from a try-catch paradigm to a data-inspection paradigm. Instead of catching errors, you examine the results array and decide how to handle each outcome individually.

Side-by-Side Comparison: Behavior and Return Values

Let me show you the practical differences with a real example. Imagine you're fetching data from three different microservices:

// Using Promise.all
async function fetchWithAll() {
  try {
    const results = await Promise.all([
      fetch('/api/users'),
      fetch('/api/posts'),
      fetch('/api/comments')
    ]);
    
    // If we get here, ALL requests succeeded
    const [users, posts, comments] = results;
    return { users, posts, comments };
  } catch (error) {
    // If ANY request failed, we end up here
    // We don't know which ones succeeded
    console.error('One or more requests failed:', error);
    return null;
  }
}
 
// Using Promise.allSettled
async function fetchWithAllSettled() {
  const results = await Promise.allSettled([
    fetch('/api/users'),
    fetch('/api/posts'),
    fetch('/api/comments')
  ]);
  
  // We ALWAYS get here with all results
  const users = results[0].status === 'fulfilled' ? results[0].value : null;
  const posts = results[1].status === 'fulfilled' ? results[1].value : null;
  const comments = results[2].status === 'fulfilled' ? results[2].value : null;
  
  return { users, posts, comments };
}

In other words, Promise.all gives you an all-or-nothing response, while Promise.allSettled gives you detailed information about each operation. The choice between them fundamentally changes how your application handles partial failures.

Real-World Use Cases: When to Use Each Method

I cannot stress this enough: choosing the wrong method can lead to poor user experiences or even data loss. Here's how I think about the decision:

Use Promise.all when your operations are interdependent. If you're fetching a user profile and their permissions, and you can't render anything meaningful without both pieces of data, Promise.all is perfect. The fast-fail behavior actually helps because there's no point continuing if critical data is missing.

Use Promise.allSettled when operations are independent. A dashboard that displays analytics from multiple sources is a classic example. If the sales data fails to load, you still want to show the traffic data and conversion metrics. Your users get partial information rather than nothing at all.

I was once working on a notification system that sent messages through email, SMS, and push notifications. Using Promise.all was a disaster because if the email service was down, we'd abort the SMS and push notifications too. Switching to Promise.allSettled meant we could deliver messages through whatever channels were available.

Promise.allSettled results structure

Code Examples: Fetching Multiple APIs

Let me share a more complete example from a recent project. We needed to aggregate data from multiple third-party APIs for a reporting dashboard:

interface APIResponse {
  source: string;
  data: any;
  error?: string;
}
 
async function aggregateReports(): Promise<APIResponse[]> {
  const apiCalls = [
    fetch('https://api.service1.com/report').then(r => r.json()),
    fetch('https://api.service2.com/metrics').then(r => r.json()),
    fetch('https://api.service3.com/analytics').then(r => r.json())
  ];
  
  const results = await Promise.allSettled(apiCalls);
  
  return results.map((result, index) => {
    const sources = ['Service 1', 'Service 2', 'Service 3'];
    
    if (result.status === 'fulfilled') {
      return {
        source: sources[index],
        data: result.value
      };
    } else {
      return {
        source: sources[index],
        data: null,
        error: result.reason.message
      };
    }
  });
}
 
// Usage
const reports = await aggregateReports();
const successfulReports = reports.filter(r => !r.error);
const failedReports = reports.filter(r => r.error);
 
console.log(`Successfully fetched ${successfulReports.length} reports`);
if (failedReports.length > 0) {
  console.warn('Some reports failed:', failedReports);
}

This approach is wonderful because it gives you full visibility into what succeeded and what failed. You can render partial results, log specific failures for monitoring, and provide meaningful feedback to users about which data sources are currently unavailable.

Error Handling Strategies for Each Method

Luckily we can implement robust error handling for both methods, but the patterns are quite different. With Promise.all, you're working with exceptions:

async function processOrders(orderIds) {
  try {
    const orders = await Promise.all(
      orderIds.map(id => fetchOrder(id))
    );
    
    // Process all orders
    return processOrderBatch(orders);
  } catch (error) {
    // Log the error for monitoring
    console.error('Order batch failed:', error);
    
    // Retry the entire batch or return error
    throw new Error('Failed to process orders');
  }
}

With Promise.allSettled, you're examining result objects:

async function processOrdersResilient(orderIds) {
  const results = await Promise.allSettled(
    orderIds.map(id => fetchOrder(id))
  );
  
  const successful = [];
  const failed = [];
  
  results.forEach((result, index) => {
    if (result.status === 'fulfilled') {
      successful.push(result.value);
    } else {
      failed.push({
        orderId: orderIds[index],
        error: result.reason
      });
    }
  });
  
  // Process what we can
  const processed = processOrderBatch(successful);
  
  // Log failures for retry
  if (failed.length > 0) {
    console.warn(`${failed.length} orders failed:`, failed);
    // Queue for retry
    queueFailedOrders(failed);
  }
  
  return {
    processed,
    failed: failed.length
  };
}

The Promise.allSettled pattern gives you much more flexibility in how you handle partial failures. You can retry just the failed operations, log detailed error information, or provide granular feedback to users.

Making the Right Choice for Your Application

When I'm deciding between these methods, I ask myself one question: "If one operation fails, should the entire process fail?" If the answer is yes, I reach for Promise.all. If the answer is no, Promise.allSettled is almost always the better choice.

In practice, I find myself using Promise.allSettled more often than Promise.all in production applications. Most real-world scenarios benefit from resilience and partial success handling. Your users would rather see some data than an error message.

However, Promise.all still has its place. For critical operations like payment processing where you need user data, payment method validation, and fraud checks all to succeed, the fail-fast behavior is exactly what you want. It prevents you from getting into inconsistent states where some operations succeeded and others didn't.

The beauty of understanding both methods is that you can mix them. You might use Promise.all for critical operations that must all succeed, while using Promise.allSettled for optional enhancements or analytics that shouldn't block the main flow.

And that concludes the end of this post! I hope you found this valuable and look out for more in the future!