Understanding JavaScript Promises: From Basic Promises to Promise.all and Promise.allSettled
Author
Md Arshad Khan
Date Published

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 Promise2const 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};1314// Usage15fetchUser(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 everything2try {3 const results = await Promise.all([4 fetchData("/api/users"), // Success5 fetchData("/api/invalid"), // Fails here6 fetchData("/api/comments") // Success, but result lost7 ]);8} catch (error) {9 // Error from /api/invalid10 // We lose the successful results from users and comments11}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 };34const results = await Promise.allSettled([5 fetchData<UserData>("/api/users"),6 fetchData<PostData>("/api/invalid"),7 fetchData<UserData>("/api/comments")8]);910// Process results11results.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};78const loadDashboard = async (): Promise<DashboardData> => {9 const results = await Promise.allSettled([10 fetchUsers(),11 fetchAnalytics(),12 fetchNotifications()13 ]);1415 const DashboardData = {16 users: null,17 analytics: null,18 notifications: null,19 errors: []20 };2122 if (results[0].status === "fulfilled") {23 data.users = results[0].value;24 } else {25 data.errors.push("Failed to load users");26 }2728 if (results[1].status === "fulfilled") {29 data.analytics = results[1].value;30 } else {31 data.errors.push("Failed to load analytics");32 }3334 if (results[2].status === "fulfilled") {35 data.notifications = results[2].value;36 } else {37 data.errors.push("Failed to load notifications");38 }3940 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};56const 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};2122// Usage23const results = await Promise.allSettled([24 fetchData("/api/endpoint1"),25 fetchData("/api/endpoint2"),26 fetchData("/api/endpoint3")27]);2829const { 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.