Mastering JavaScript Promise Concurrency Methods

A comprehensive guide to Promise.all(), Promise.allSettled(), Promise.any(), and Promise.race() for building performant web applications.

Understanding Promise Concurrency in JavaScript

Modern JavaScript applications frequently need to coordinate multiple asynchronous operations. Whether you're fetching data from multiple APIs, processing a batch of files, or handling concurrent user requests, understanding how to effectively manage promise concurrency is essential for building performant, reliable web applications.

The promise concurrency methods built into JavaScript provide clean ways to express complex async patterns. These methods accept an iterable of promises and return a new promise based on the outcomes of the input promises. What distinguishes them is the specific condition under which they resolve or reject, and the shape of the data they return.

Key points covered in this guide:

  • Promise.all(): When all promises must succeed together
  • Promise.allSettled(): Getting results from all promises regardless of failure
  • Promise.any(): Getting the first successful result
  • Promise.race(): Getting the first settled result
  • Error handling strategies for each method
  • Performance considerations and best practices

4

Promise Concurrency Methods

2015

Promise.all() Available Since

2020

Promise.allSettled() Available Since

Promise.all(): Requiring Complete Success

Promise.all() is the most well-known promise concurrency method, designed for scenarios where you need all operations to succeed. It takes an iterable of promises as input and returns a single Promise that fulfills when all of the input's promises fulfill, or rejects when any of the input's promises rejects with the first rejection reason.

The behavior of Promise.all() makes it ideal for scenarios where the operations are interdependent--where partial results are meaningless or could lead to inconsistent application state. This is particularly relevant when building APIs that require multiple data sources to function correctly, as covered in our API errors guide.

Code Example

async function loadDashboardData() {
 const [user, posts, notifications, settings] = await Promise.all([
 fetchUserProfile(userId),
 fetchUserPosts(userId),
 fetchNotifications(userId),
 fetchUserSettings(userId)
 ]);

 return { user, posts, notifications, settings };
}

In this example, Promise.all() ensures that either all four data fetches complete successfully, or the entire operation fails. If any single fetch rejects, Promise.all() immediately rejects with that error, without waiting for the remaining promises to settle.

Key Characteristics

  • Fail-fast behavior: Rejects immediately upon first rejection
  • Order preservation: Results returned in the same order as input promises
  • Non-promise handling: Non-promise values are ignored but counted in the result array
  • Use case: When all operations are required for success

According to the MDN Web Docs, Promise.all() takes an iterable of promises and returns a single Promise that fulfills when all of the input's promises fulfill, or rejects when any of the input's promises rejects.

Promise.allSettled(): Collecting Results Regardless of Success or Failure

Introduced to JavaScript in 2020, Promise.allSettled() provides a more forgiving approach to promise concurrency. It takes an iterable of promises and returns a single Promise that fulfills when all of the input's promises have settled (either fulfilled or rejected). Unlike Promise.all(), Promise.allSettled() never rejects--it always resolves.

The resolved value is an array of "outcome objects," each describing the result of one input promise:

  • status: Either "fulfilled" or "rejected"
  • value: Present only if status is "fulfilled"
  • reason: Present only if status is "rejected"

Code Example

async function processBatchOfFiles(files) {
 const results = await Promise.allSettled(
 files.map(file => processFile(file))
 );

 const successful = results.filter(r => r.status === 'fulfilled');
 const failed = results.filter(r => r.status === 'rejected');

 console.log(`Processed ${successful.length} files successfully`);
 console.log(`Failed to process ${failed.length} files`);

 return { successful, failed };
}

This approach is particularly valuable for batch processing operations where you want to continue processing all items even if some fail. When combined with proper cloud hosting infrastructure, you can build resilient systems that handle partial failures gracefully. As noted in LogRocket's analysis of modern async patterns, Promise.allSettled() is typically used when you have multiple asynchronous tasks that are not dependent on one another to complete successfully.

Key Characteristics

  • Comprehensive results: Returns outcome for every promise
  • Never rejects: Always resolves with an array of outcomes
  • Use case: Batch processing where partial success is acceptable
  • Error handling: Inspect each result to determine success or failure
Promise Concurrency Methods Comparison

Promise.all()

Resolves when ALL promises fulfill. Rejects on FIRST rejection. Use when all operations must succeed.

Promise.allSettled()

Resolves when ALL promises settle. Returns outcome for each promise. Use for batch processing with partial success.

Promise.any()

Resolves on FIRST fulfillment. Rejects only if ALL promises reject. Use for redundant sources and latency optimization.

Promise.race()

Resolves or rejects on FIRST settlement. Use for implementing timeouts and deadlines.

Promise.any(): Getting the First Success

Promise.any() resolves as soon as any one of the input promises fulfills, ignoring failures until all promises have failed. It takes an iterable of promises and returns a new Promise that fulfills with the value from the first promise that fulfills.

Promise.any() only rejects if all input promises reject, in which case it throws an AggregateError containing all rejection reasons.

Code Example

async function fetchWithFallback(urls) {
 const promises = urls.map(url =>
 fetch(url).then(response => response.json())
 );

 try {
 return await Promise.any(promises);
 } catch (errors) {
 throw new Error('All fetch attempts failed');
 }
}

This pattern is particularly common in systems that prioritize latency and availability over consistency. Querying multiple redundant data sources and using whichever responds first minimizes user-perceived latency. Combined with HTTP/3 protocol improvements, you can achieve even faster response times across distributed systems.

Key Characteristics

  • First success wins: Resolves with the first fulfilled promise
  • All must fail to reject: Only rejects if every promise rejects
  • Use case: Redundant sources, latency optimization
  • Note: Slower promises continue executing in the background

The MDN Web Docs explain that Promise.any() fulfills with the value from the first promise in the iterable that fulfills, or rejects with an AggregateError containing the reasons from all input promises if all of them reject.

Promise.race(): Racing Any Settlement

Promise.race() settles based on whichever promise settles first, whether that settlement is a fulfillment or a rejection. It takes an iterable of promises and returns a new Promise that settles with the same outcome as the first promise in the iterable to settle.

This method is essential for implementing timeouts or deadline-based operations.

Code Example: Implementing Timeouts

async function fetchWithTimeout(url, timeoutMs = 5000) {
 const timeoutPromise = new Promise((_, reject) =>
 setTimeout(() => reject(new Error('Request timeout')), timeoutMs)
 );

 const fetchPromise = fetch(url).then(response => response.json());

 return Promise.race([fetchPromise, timeoutPromise]);
}

In this pattern, the timeout promise rejects after the specified duration. If the fetch completes first, its result is returned; if the timeout fires first, the error is thrown instead.

Key Characteristics

  • First settlement wins: Adopts whatever outcome the first promise produces
  • Timeout patterns: Essential for deadline-based operations
  • Use case: Timeouts, circuit breakers, deadline enforcement
  • Warning: Can resolve OR reject based on first settlement

According to MDN documentation, Promise.race() returns a new promise that settles with the same outcome as the first promise in the iterable to settle.

Error Handling Strategies for Promise Concurrency

Proper error handling is perhaps the most critical aspect of working with promise concurrency methods. Each method handles errors differently, requiring tailored approaches.

Promise.all() Error Handling

With Promise.all(), a single rejection causes the entire operation to fail. Wrap the call in a try-catch block:

async function loadCriticalData() {
 try {
 const results = await Promise.all([
 fetchCriticalData(),
 fetchAuthentication(),
 fetchConfiguration()
 ]);
 return results;
 } catch (error) {
 console.error('Critical data load failed:', error);
 throw new Error('Application cannot start without required data');
 }
}

Promise.allSettled() Error Handling

Since Promise.allSettled() never rejects, inspect each result:

async function robustBatchProcess(tasks) {
 const results = await Promise.allSettled(tasks.map(task => executeTask(task)));

 return results.map((result, index) => {
 if (result.status === 'fulfilled') {
 return { success: true, data: result.value };
 } else {
 return {
 success: false,
 error: result.reason,
 taskIndex: index,
 canRetry: isRetryable(result.reason)
 };
 }
 });
}

Promise.any() and Promise.race() Error Handling

Promise.any() only rejects when all promises fail (AggregateError), while Promise.race() adopts whatever outcome the first promise produces. For robust error handling, wrap Promise.race() operations in try-catch blocks since they can reject. Understanding these error patterns helps prevent common API errors in production systems.

Choosing the right promise method depends on your specific use case and what you need to accomplish, as highlighted by LogRocket's exploration of modern async patterns.

Performance Considerations and Best Practices

When working with promise concurrency methods, performance is about more than just execution speed--it's about resource management, user experience, and system reliability.

Common Misconception

A common mistake is assuming that Promise.all() provides parallel execution. While Promise.all() allows multiple async operations to proceed concurrently, they still execute on JavaScript's single-threaded event loop. The actual parallelism comes from I/O operations being handled by the browser or Node.js runtime while JavaScript continues executing. For CPU-intensive work, you'll need Web Workers to achieve true parallelism.

Best Practices

Process results as they arrive when appropriate:

async function processWithProgressiveResults(promises) {
 for (const promise of promises) {
 try {
 const result = await promise;
 handleResult(result);
 } catch (error) {
 handleError(error);
 }
 }
}

Use chunking for large batches:

For large batches of operations, process promises in smaller chunks rather than all at once. This prevents overwhelming system resources and provides better progress feedback.

When to Use Each Method

ScenarioRecommended Method
All operations must succeedPromise.all()
Need results from all regardless of outcomePromise.allSettled()
Want fastest available resultPromise.any()
Need to establish deadlinesPromise.race()

Dashboard Initialization: Loading user session, fetching initial data, and establishing connections where partial initialization doesn't make sense.

API Dependencies: When subsequent operations depend on the results of previous ones, ensuring data consistency.

Frequently Asked Questions

Does Promise.all() execute promises in parallel?

No. Promise.all() allows promises to progress concurrently, but JavaScript remains single-threaded. True parallelism requires Web Workers for CPU-intensive work.

What happens to other promises if one rejects in Promise.all()?

They continue executing but their results are discarded. The first rejection causes Promise.all() to reject immediately with that error.

Can Promise.allSettled() ever reject?

No. Promise.allSettled() always resolves with an array of outcome objects describing each promise's result, whether fulfilled or rejected.

When should I use Promise.any() instead of Promise.race()?

Use Promise.any() when you only care about successful results and want the fastest success. Use Promise.race() when you need to respond to either success or failure, such as implementing timeouts.

Are cancelled promises in Promise.any() still running?

Yes. Promises that lose the race continue executing in the background--they're not automatically cancelled. Implement your own cancellation if needed.

Conclusion

Mastering JavaScript's promise concurrency methods is essential for building modern, responsive web applications. Each method serves a distinct purpose:

  • Promise.all() when all operations must succeed together
  • Promise.allSettled() when you need results from every operation regardless of outcome
  • Promise.any() when you want the fastest available success
  • Promise.race() when you need to establish deadlines or respond to the first settlement

By understanding their behaviors, error handling characteristics, and appropriate use cases, you can write more robust, performant async code. The key is matching the method to your semantic requirements--choosing the right tool for each specific job.

With these tools at your disposal, you can tackle complex async patterns with confidence and clarity, building applications that are both performant and reliable. For more insights into building high-performance JavaScript applications, explore our web development services or contact our team to discuss your project.

Need Help Building Performant JavaScript Applications?

Our team of experienced developers can help you implement modern JavaScript patterns and build high-performance web applications.