Md Arshad Khan | Full Stack Engineer Logo
frontend,  React,  Javascript

Understanding JavaScript Promises: From Basic Promises to Promise.all and Promise.allSettled

Author

Md Arshad Khan

Date Published

Understanding JS Promises blog hero image

Async operations are everywhere in frontend development: API calls, file uploads, database queries. Promises give us a clean way to handle these operations without callback hell. Let’s break down Promises and their powerful utility methods with practical examples.

What Are Promises?

A Promise represents a value that will be available now, later, or never. It has three states: pending (initial), fulfilled (success), or rejected (error).

1// Basic Promise
2const fetchUser = (id: number): Promise<User> => {
3 return new Promise((resolve, reject) => {
4 setTimeout(() => {
5 if (id > 0) {
6 resolve({ id, name: "John Doe" });
7 } else {
8 reject(new Error("Invalid user ID"));
9 }
10 }, 1000);
11 });
12};
13
14// Usage
15fetchUser(1)
16 .then(user => console.log(user))
17 .catch(error => console.error(error));
18

The Problem: Multiple Parallel Requests

Imagine you need to fetch data from three APIs simultaneously. Calling them one after another is slow:

1// Slow: Sequential execution (3 seconds total)
2const data1 = await fetchData("/api/users");
3const data2 = await fetchData("/api/posts");
4const data3 = await fetchData("/api/comments");
5

This takes 3 seconds if each request takes 1 second. We can do better.

Solution 1: Promise.all for Parallel Execution

Promise.all  runs all promises simultaneously and waits for all to complete. It returns an array of results in the same order as the input promises.

1// Fast: Parallel execution (1 second total)
2const [users, posts, comments] = await Promise.all([
3 fetchData("/api/users"),
4 fetchData("/api/posts"),
5 fetchData("/api/comments")
6]);
7

The Catch: If any promise rejects,  Promise.all  immediately rejects with that error. The other promises continue running, but their results are lost.

1// Example: One failure breaks everything
2try {
3 const results = await Promise.all([
4 fetchData("/api/users"), // Success
5 fetchData("/api/invalid"), // Fails here
6 fetchData("/api/comments") // Success, but result lost
7 ]);
8} catch (error) {
9 // Error from /api/invalid
10 // We lose the successful results from users and comments
11}
12

Solution 2: Promise.allSettled for Robust Error Handling

 Promise.allSettled  waits for all promises to complete, regardless of success or failure. It returns an array of objects describing each result.

1type UserData = { id: number; name: string };
2type PostData = { id: number; title: string };
3
4const results = await Promise.allSettled([
5 fetchData<UserData>("/api/users"),
6 fetchData<PostData>("/api/invalid"),
7 fetchData<UserData>("/api/comments")
8]);
9
10// Process results
11results.forEach((result, index) => {
12 if (result.status === "fulfilled") {
13 console.log(`Request ${index} succeeded:`, result.value);
14 } else {
15 console.error(`Request ${index} failed:`, result.reason);
16 }
17});
18

Each result object has:

 status : “fulfilled” or “rejected”

 value : the resolved value (if fulfilled)

 reason : the error (if rejected)

Real-World Example: Dashboard with Multiple APIs

Here’s a practical React example loading dashboard

1type DashboardData = {
2 users: User[] | null;
3 analytics: Analytics | null;
4 notifications: Notification[] | null;
5 errors: string[];
6};
7
8const loadDashboard = async (): Promise<DashboardData> => {
9 const results = await Promise.allSettled([
10 fetchUsers(),
11 fetchAnalytics(),
12 fetchNotifications()
13 ]);
14
15 const DashboardData = {
16 users: null,
17 analytics: null,
18 notifications: null,
19 errors: []
20 };
21
22 if (results[0].status === "fulfilled") {
23 data.users = results[0].value;
24 } else {
25 data.errors.push("Failed to load users");
26 }
27
28 if (results[1].status === "fulfilled") {
29 data.analytics = results[1].value;
30 } else {
31 data.errors.push("Failed to load analytics");
32 }
33
34 if (results[2].status === "fulfilled") {
35 data.notifications = results[2].value;
36 } else {
37 data.errors.push("Failed to load notifications");
38 }
39
40 return data;
41};
42

This approach shows partial data even when some APIs fail, giving users a better experience than a blank error screen.

Type-Safe Helper for Promise.allSettled

Here’s a reusable TypeScript helper:

1type SettledResult<T> = {
2 success: T[];
3 errors: Error[];
4};
5
6const getSettledResults = <T>(
7 results: PromiseSettledResult<T>[]
8): SettledResult<T> => {
9 return results.reduce(
10 (acc, result) => {
11 if (result.status === "fulfilled") {
12 acc.success.push(result.value);
13 } else {
14 acc.errors.push(result.reason);
15 }
16 return acc;
17 },
18 { success: [], errors: [] } as SettledResult<T>
19 );
20};
21
22// Usage
23const results = await Promise.allSettled([
24 fetchData("/api/endpoint1"),
25 fetchData("/api/endpoint2"),
26 fetchData("/api/endpoint3")
27]);
28
29const { success, errors } = getSettledResults(results);
30console.log(`Success: ${success.length}, Errors: ${errors.length}`);
31


When to Use Each Approach

Use Promise.all when:

All operations must succeed for the result to be valid

You want fast failure (fail-fast behavior)

Example: Multi-step form submission where all steps must complete

Use Promise.allSettled when:

Partial success is acceptable

You want to collect all results before deciding what to do

Example: Loading multiple dashboard widgets where some can fail

Performance Tip

Both methods run promises in parallel, making them much faster than sequential  await  calls. On a dashboard loading 5 API endpoints that each take 200ms:

Sequential: 1000ms (200ms × 5)

Parallel: 200ms (all at once)

That’s a 5x performance improvement just by using  Promise.all  or  Promise.allSettled .

Summary

Promises are the foundation of async JavaScript.  Promise.all  speeds up parallel operations but fails fast.  Promise.allSettled  gives you complete control by always returning all results, making your apps more resilient to partial failures.