VOOZH about

URL: https://blog.logrocket.com/promise-all-modern-async-patterns/

⇱ Is Promise.all still relevant in 2025? - LogRocket Blog


2025-10-29
2117
#vanilla javascript
Leonardo Maldonado
23610
102
👁 Image

See how LogRocket's Galileo AI surfaces the most severe issues for you

No signup required

Check it out

Before promises were introduced natively in JavaScript, we used a lot of callbacks for asynchronous tasks. It’s pretty common to see callbacks being used for asynchronous tasks because a lot of developers might still think that callbacks and promises are the same, but in fact, they are not.

👁 Understanding Promise.all in JavaScript

When promises were introduced natively in JavaScript, it was definitely a game-changer. In a lot of projects, the usage of callbacks was replaced by promises for running asynchronous tasks, and promises became the main alternative to it. Promises resemble callbacks in some ways, but with an easier-to-follow syntax and a better understanding of the code.

In 2025, JavaScript’s async concept has evolved dramatically. With async/await, Promise.allSettled, Promise.any, and the new Array.fromAsync(), developers often wonder whether Promise.all is still worth using. The answer is absolutely yes, but with important caveats.

In this article, we’re going to cover exactly when the Promise.all method shines and how to combine it with modern async patterns.

To understand how the Promise.all method works, first, we need to understand how promises work in JavaScript.

Editor’s note: This article was last updated in October 2025 to reflect modern JavaScript features introduced since its original publication. It now includes recent developments, such as Array.fromAsync() and revisits how methods like Promise.all, Promise.allSettled, and Promise.any fit into today’s async workflows.

🚀 Sign up for The Replay newsletter

The Replay is a weekly newsletter for dev and engineering leaders.

Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.

Promises

JavaScript is single-threaded, which means that we can only run one block of code at a time. It executes code in order and must finish executing code before running the next one. A promise represents the future result of an asynchronous operation. Promises are often used to handle asynchronous tasks in JavaScript.

A promise is an object that will return a value in the future; it can either be a resolved value, which means the promise was successful, or a rejected value, which means that an error occurred. A promise will only return a value once, which means that if a promise returns an error, it will only return it once.

A promise has three possible mutually exclusive states:

  • fulfilled  —  a promise is fulfilled if promise.then(f) will call f “as soon as possible”
  • rejected  —  a promise is rejected if promise.then(undefined, r) will call r “as soon as possible”
  • pending  —  a promise is pending if it is neither fulfilled nor rejected

Sometimes we might hear that a promise is settled. That means that this promise is either fulfilled or rejected, settled is not a state but it’s used just for convenience.

To create a promise, we use the new keyword, and inside the Promise object, we pass a function. This function is called executor, and it takes two arguments, resolve for success and reject for error:

const firstPromise = new Promise((resolve, reject) => { 
 ... 
});

Inside the promise, there’s a condition and this is where you put your logic. In case the condition is met, we use the resolve argument to return success for us. In case there’s an error, the reject argument will return an error for the promise:

const firstPromise = new Promise((resolve, reject) => {
 const sum = () => 1 + 1;
 if (sum() === 2) resolve("Success");
 else reject("Error");
});

But in real applications, you’ll work with promises returned by APIs, not create them manually.

Chaining

Promise chaining is one of the things that makes promises so great and easy to use. We can execute a chain of asynchronous tasks, each task will be executed as soon as the previous task was completed.

We can chain our promise using a .then block, anything returned from this block becomes a resolved promise:

const firstPromise = new Promise((resolve, reject) => {
 const sum = () => 1 + 1;
 if (sum() === 2) resolve("Success");
 else reject("Error");
});
firstPromise
 .then(success => console.log("success: ", success));

The beauty of the .then block is that we can perform additional async actions one after another. For error handling, we can use the .catch block:

const firstPromise = new Promise((resolve, reject) => {
 const sum = () => 1 + 1;
 if (sum() === 2) resolve("Success");
 else reject("Error");
});
firstPromise
 .then(success => console.log("success: ", success))
 .catch(error => console.log("error: ", error));

You can perform asynchronous operations by using callbacks or promises. But there are differences.


Over 200k developers use LogRocket to create better digital experiences

👁 Image
Learn more →

If you are using callbacks to perform asynchronous operations, in some cases you might end up having too many nested functions, this is what is called callback hell. Too many nested functions can cause your code to be unreadable and unmanageable. You can solve it by using promises, with promises you can have more readable and manageable code.

Promises are a cleaner way to run asynchronous tasks. Promises provide catch mechanism, which callbacks do not have. Promises allow cleaner, better, and functional code.

With modern JavaScript, we use async/await instead of promise chaining because it’s cleaner, more readable, and easier to debug.

Old chaining syntax:

fetchUserData()
 .then(user => fetchUserPosts(user.id))
 .then(posts => fetchPostComments(posts[0].id))
 .then(comments => console.log(comments))
 .catch(error => console.error('Something failed:', error));

Modern chaining syntax:

try {
 const user = await fetchUserData();
 const posts = await fetchUserPosts(user.id);
 const comments = await fetchPostComments(posts[0].id);
 console.log(comments);
} catch (error) {
 console.error('Something failed:', error);
}

The async/await syntax eliminates callback hell and makes error handling straightforward. But here’s the key insight: async/await is sequential by default. When you need concurrency (running multiple operations simultaneously), which is dramatically faster than sequential await calls, that’s where Promise.all becomes essential.

Now that we covered a little bit about promises, let’s look at Promise.all.

Promise.all

The Promise.all method takes asynchronous operations to a whole new level and helps us to aggregate and perform a group of promises in JavaScript.

Promise.all is just a promise that receives an array of promises as an input. It gets resolved when all the promises get resolved or gets rejected if one of the promises gets rejected.

You accumulated a lot of promises in your code, and you want to perform all these asynchronous operations once, without having to use some strange thing for it, like a for loop, for example. How can you do it?

You have two choices here that you can use for this use case:

  1. You can perform all the promises one by one – you can run these promises one by one or chain them and process the data as soon as it is available
  2. You can perform all promises passing them as an array input to Promise.all and the method will return a value

The better solution to use in this case is to use the Promise.all method. It will perform all the promises, return a single promise, and resolve when all of the promises passed are resolved:

const allpromises = Promise.all([Promise1, Promise2, Promise3, Promise4, ...]);

Remember, the Promise.all method will only return resolve if all the promises passed in the array returns successfully. In case there’s only one promise in the array that returns rejected, the Promise.all method will return rejected.

Here’s the fundamental rule: Promise.all fails fast. If any promise rejects, the entire operation fails immediately.

For example, let’s imagine that we have a function called sum. This function will just return the value of some operation for us:

const sum = (a, b) => a + b;

Now, let’s imagine that we have five promises, and inside each one of these promises we’re going to use the sum function and inside an if statement, compare the value. In case it’s true, we are going to return a success message and in case it’s false we are going to return an error message:

const first = new Promise((resolve, reject) => {
 const value = sum(1, 1);
 if (value === 2) resolve(value);
 else reject(value);
});

const second = new Promise((resolve, reject) => {
 const value = sum(2, 2);
 if (value === 4) resolve(value);
 else reject(value);
});

const third = new Promise((resolve, reject) => {
 const value = sum(3, 3);
 if (value === 6) resolve(value);
 else reject(value);
});

const fourth = new Promise((resolve, reject) => {
 const value = sum(4, 4);
 if (value === 8) resolve(value);
 else reject(value);
});

const fifth = new Promise((resolve, reject) => {
 const value = sum(5, 5);
 if (value === 10) resolve(value);
 else reject(value);
});

To perform all promises at once, we pass an array input to Promise.all:

const allPromises = Promise.all([first, second, third, fourth, fifth]);

Now, we just call our single promise called allPromises and it will return to us an array of resolved values:

allpromises.then(success => console.log('sucess: ', success)).catch(error => console.log('error: ', error));
// Result
// sucess: [ 2, 4, 2, 8, 10 ]

In case one of the promises returns an error, our single promise will return an error as well. In our example, inside the fifth promise, we are going to pass as arguments for the sum function the values 5 and 6.

Of course, this will return an error as 5 + 6 is not 10. This will cause our single promise to return an error:

const fifth = new Promise((resolve, reject) => { const value = sum(5, 6); if (value === 10) resolve(value); else reject(value); }); const allpromises = Promise.all([first, second, third, fourth, fifth]); allpromises.then(success => console.log(‘sucess: ‘, success)).catch(error => console.log(‘error: ‘, error)); // Result // error: 11

With Promise.all, if any of the promise fails, you lose all data.

Modern Promise methods (allSettled, any, and race)

Promise.allSettled

You have many promises that you want to perform but the Promise.all might not be the best solution for you if you want to return all the values, regardless if there is an error in your promises.


More great articles from LogRocket:


You can use the Promise.allSettled method for it. This method will return a single promise that will be resolved after all the promises were either fulfilled or rejected.

Let’s use our last example, and instead of using the Promise.all method, we are going to use the Promise.allSettled method:

const allpromises = Promise.allSettled([first, second, third, fourth, fifth]);
allpromises.then(success => console.log('sucess: ', success)).catch(error => console.log('error: ', error));

// Result
// success: [
// { status: 'fulfilled', value: 2 },
// { status: 'fulfilled', value: 4 },
// { status: 'fulfilled', value: 6 },
// { status: 'fulfilled', value: 8 },
// { status: 'rejected', reason: 11 }
// ]

Promise.any

Promise.any comes handy when you want the first successful result in the promise array.

const data = await Promise.any([
 fetch('/api/primary-source'),
 fetch('/api/backup-source'),
 fetch('/api/cache-source')
]);

This will return data from the first API that responds.

Promise.race

When you want the first result (success or failure) use the Promise.race method.

const timeout = new Promise((_, reject) => 
 setTimeout(() => reject(new Error('Timeout')), 5000)
);

const data = await Promise.race([
 fetch('/api/slow-endpoint'),
 timeout
]);

Array.fromAsync()

Array.fromAsync() is the sequential counterpart to Promise.all()‘s concurrent approach. While Promise.all() processes everything simultaneously, Array.fromAsync() takes a more measured, one-at-a-time strategy.

Array.fromAsync() is like for await...of wrapped in a convenient function. It processes async iterables sequentially, waiting for each item to complete before moving to the next.

This is essentially what Array.fromAsync() does under the hood:

async function sequentialProcessor(items) {
 const results = [];
 for await (const item of items) {
 results.push(await item);
 }
 return results;
}

Interestingly, both Array.fromAsync() and Promise.all() can convert an iterable of promises into a resolved array, but they do it in completely opposite ways.

Array.fromAsync() processes promises one by one, waits for each promise to settle before starting the next and it’s perfect for rate-limited APIs or memory-constrained scenarios.

On the other hand, Promise.all() processes all promises simultaneously, waits for everything to complete at once and is ideal when you need maximum speed and all operations can run in parallel.

When it comes to performance, Array.fromAsync() takes longer because it processes each promise sequentially, while Promise.all() completes in roughly the time of the slowest individual promise.

When to use

To use the Promise.all method, you need to know first what you need to achieve:

Use Promise.all:

  1. When the async tasks that you are performing are independent of each other
  2. If you want to know whether all of the promises have finished successfully
  3. When yuou need to make requests to different APIs and after all the responses you want to do something with the result
  4. If you want fail-fast behaviour.

Avoid Promise.all:

  1. When some failures are acceptable (use Promise.allSettled)
  2. When you only need one success (use Promise.any)
  3. If you need timeout handling (use Promise.race)
  4. When operations depend on each other (use sequential await)

Conclusion

In this article, we covered a little bit about promises in JavaScript and learned more about  Promise.all, modern promise methods and Array.fromAsync(). These methods are very helpful when you need to aggregate and perform many promises and return a single promise with all the values inside an array.

LogRocket: Debug JavaScript errors more easily by understanding the context

Debugging code is always a tedious task. But the more you understand your errors, the easier it is to fix them.

LogRocket allows you to understand these errors in new and unique ways. Our frontend monitoring solution tracks user engagement with your JavaScript frontends to give you the ability to see exactly what the user did that led to an error.

👁 LogRocket Dashboard Free Trial Banner

LogRocket records console logs, page load times, stack traces, slow network requests/responses with headers + bodies, browser metadata, and custom logs. Understanding the impact of your JavaScript code will never be easier!

Try it for free.

👁 Image
👁 Image
👁 Image

Stop guessing about your digital experience with LogRocket

Get started for free

Recent posts:

What is TSRX?: What JSX would look like if it were designed today

TSRX adds first-class control flow, conditional hooks, and scoped styles to React via a TypeScript compiler extension — no new framework required.

👁 Image
Ikeh Akinyemi
Jun 12, 2026 ⋅ 6 min read

How to add authentication to a React Native app with Better Auth

Learn how to build a full React Native auth system using Better Auth and Expo — with email/password login, Google OAuth, session persistence, and protected routes.

👁 Image
Chinwike Maduabuchi
Jun 9, 2026 ⋅ 13 min read

AI dev tool power rankings & comparison [June 2026]

Compare the top AI development tools and models of June 2026. View updated rankings, feature breakdowns, and find the best fit for you.

👁 Image
Chizaram Ken
Jun 8, 2026 ⋅ 11 min read

How to check username availability at scale with Bloom filters

Learn how Bloom filters reduce database lookups for username availability checks while preserving correctness at scale.

👁 Image
Rosario De Chiara
Jun 8, 2026 ⋅ 6 min read
View all posts

Hey there, want to help make our blog better?

Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.

Sign up now