Promise Methods
JavaScript provides powerful static methods for working with multiple Promises simultaneously. Understanding these methods is essential for building efficient asynchronous applications. In this lesson, we'll explore Promise.all(), Promise.race(), Promise.allSettled(), Promise.any(), and practical patterns for using them.
Promise.all() - Wait for All Promises
Promise.all() takes an array of Promises and returns a single Promise that resolves when all input Promises have resolved, or rejects if any Promise rejects.
const promise1 = Promise.resolve(3);
const promise2 = Promise.resolve(42);
const promise3 = Promise.resolve("Hello");
Promise.all([promise1, promise2, promise3])
.then((values) => {
console.log(values); // [3, 42, "Hello"]
});
// Practical example: Fetching multiple users
function fetchUser(id) {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ id, name: `User${id}` });
}, Math.random() * 1000);
});
}
Promise.all([
fetchUser(1),
fetchUser(2),
fetchUser(3)
])
.then((users) => {
console.log("All users loaded:", users);
// Output: All users loaded: [
// { id: 1, name: "User1" },
// { id: 2, name: "User2" },
// { id: 3, name: "User3" }
// ]
})
.catch((error) => {
console.error("Failed to load users:", error);
});
Key Behavior: Promise.all() fails fast - if any Promise rejects, the entire operation rejects immediately, even if other Promises are still pending.
Promise.all() with Rejection
Understanding how Promise.all() handles failures is crucial:
const promise1 = Promise.resolve("Success 1");
const promise2 = Promise.reject("Error in promise 2");
const promise3 = Promise.resolve("Success 3");
Promise.all([promise1, promise2, promise3])
.then((results) => {
console.log("All succeeded:", results);
// This won't execute
})
.catch((error) => {
console.error("One failed:", error);
// Output: One failed: Error in promise 2
// promise1 and promise3 results are lost!
});
// Real-world example: Loading page resources
function loadCSS() {
return new Promise((resolve, reject) => {
setTimeout(() => resolve("CSS loaded"), 500);
});
}
function loadJS() {
return new Promise((resolve, reject) => {
setTimeout(() => reject("JS failed to load"), 300);
});
}
function loadImages() {
return new Promise((resolve) => {
setTimeout(() => resolve("Images loaded"), 800);
});
}
Promise.all([loadCSS(), loadJS(), loadImages()])
.then((results) => {
console.log("Page ready:", results);
})
.catch((error) => {
console.error("Page load failed:", error);
// Output: Page load failed: JS failed to load
});
Warning: With Promise.all(), a single failure causes the entire operation to fail. If you need to handle partial failures, use Promise.allSettled() instead.
Promise.race() - First to Finish Wins
Promise.race() returns a Promise that resolves or rejects as soon as one of the input Promises resolves or rejects:
const promise1 = new Promise((resolve) => {
setTimeout(() => resolve("First finished"), 500);
});
const promise2 = new Promise((resolve) => {
setTimeout(() => resolve("Second finished"), 100);
});
const promise3 = new Promise((resolve) => {
setTimeout(() => resolve("Third finished"), 300);
});
Promise.race([promise1, promise2, promise3])
.then((result) => {
console.log(result); // Output: Second finished
// Other promises continue running but their results are ignored
});
// Practical example: Request timeout
function fetchWithTimeout(url, timeout) {
const fetchPromise = fetch(url);
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject("Request timeout"), timeout);
});
return Promise.race([fetchPromise, timeoutPromise]);
}
fetchWithTimeout("https://api.example.com/data", 5000)
.then((response) => response.json())
.then((data) => console.log("Data:", data))
.catch((error) => console.error("Error:", error));
Common Promise.race() Patterns
Here are practical use cases for Promise.race():
// 1. Timeout pattern
function timeout(ms) {
return new Promise((_, reject) => {
setTimeout(() => reject(`Timeout after ${ms}ms`), ms);
});
}
function doSomethingAsync() {
return new Promise((resolve) => {
setTimeout(() => resolve("Done"), 3000);
});
}
Promise.race([
doSomethingAsync(),
timeout(2000)
])
.then((result) => console.log(result))
.catch((error) => console.error(error)); // Timeout after 2000ms
// 2. Fastest server response
function fetchFromServer1() {
return new Promise((resolve) => {
setTimeout(() => resolve("Server 1 data"), Math.random() * 1000);
});
}
function fetchFromServer2() {
return new Promise((resolve) => {
setTimeout(() => resolve("Server 2 data"), Math.random() * 1000);
});
}
function fetchFromServer3() {
return new Promise((resolve) => {
setTimeout(() => resolve("Server 3 data"), Math.random() * 1000);
});
}
Promise.race([
fetchFromServer1(),
fetchFromServer2(),
fetchFromServer3()
])
.then((data) => {
console.log("Fastest response:", data);
});
// 3. User interaction timeout
function waitForUserClick() {
return new Promise((resolve) => {
document.addEventListener('click', () => {
resolve("User clicked");
}, { once: true });
});
}
Promise.race([
waitForUserClick(),
timeout(10000)
])
.then((result) => console.log(result))
.catch(() => console.log("No user interaction"));
Promise.allSettled() - Wait for All, Keep All Results
Promise.allSettled() waits for all Promises to settle (either resolve or reject) and returns their results:
const promise1 = Promise.resolve("Success 1");
const promise2 = Promise.reject("Error 2");
const promise3 = Promise.resolve("Success 3");
const promise4 = Promise.reject("Error 4");
Promise.allSettled([promise1, promise2, promise3, promise4])
.then((results) => {
console.log(results);
// Output: [
// { status: "fulfilled", value: "Success 1" },
// { status: "rejected", reason: "Error 2" },
// { status: "fulfilled", value: "Success 3" },
// { status: "rejected", reason: "Error 4" }
// ]
results.forEach((result, index) => {
if (result.status === "fulfilled") {
console.log(`Promise ${index} succeeded:`, result.value);
} else {
console.log(`Promise ${index} failed:`, result.reason);
}
});
});
Best Practice: Use Promise.allSettled() when you need to know the outcome of all operations, regardless of whether some fail. This is perfect for batch operations where partial success is acceptable.
Practical Example: Batch File Upload
Let's use Promise.allSettled() for a real-world scenario:
function uploadFile(file) {
return new Promise((resolve, reject) => {
// Simulate upload with random success/failure
setTimeout(() => {
if (Math.random() > 0.3) {
resolve({ file: file.name, url: `https://cdn.example.com/${file.name}` });
} else {
reject(`Failed to upload ${file.name}`);
}
}, Math.random() * 2000);
});
}
const files = [
{ name: "document.pdf" },
{ name: "image1.jpg" },
{ name: "image2.jpg" },
{ name: "video.mp4" }
];
const uploadPromises = files.map(file => uploadFile(file));
Promise.allSettled(uploadPromises)
.then((results) => {
const successful = results.filter(r => r.status === "fulfilled");
const failed = results.filter(r => r.status === "rejected");
console.log(`Uploaded: ${successful.length}/${files.length} files`);
successful.forEach(result => {
console.log("✓ Success:", result.value);
});
failed.forEach(result => {
console.error("✗ Failed:", result.reason);
});
// Continue with successful uploads
const uploadedUrls = successful.map(r => r.value.url);
console.log("Uploaded URLs:", uploadedUrls);
});
Promise.any() - First Success Wins
Promise.any() resolves as soon as any Promise succeeds, ignoring rejections unless all Promises reject:
const promise1 = Promise.reject("Error 1");
const promise2 = Promise.reject("Error 2");
const promise3 = Promise.resolve("Success 3");
const promise4 = Promise.resolve("Success 4");
Promise.any([promise1, promise2, promise3, promise4])
.then((result) => {
console.log("First success:", result);
// Output: First success: Success 3
})
.catch((error) => {
console.error("All failed:", error);
});
// If all promises reject
Promise.any([
Promise.reject("Error 1"),
Promise.reject("Error 2"),
Promise.reject("Error 3")
])
.then((result) => {
console.log("Success:", result);
})
.catch((error) => {
console.error("All rejected:", error);
// Output: All rejected: AggregateError: All promises were rejected
console.log(error.errors); // ["Error 1", "Error 2", "Error 3"]
});
Promise.any() Use Cases
Practical scenarios where Promise.any() excels:
// 1. Fallback data sources
function fetchFromPrimaryAPI() {
return new Promise((resolve, reject) => {
setTimeout(() => reject("Primary API down"), 1000);
});
}
function fetchFromSecondaryAPI() {
return new Promise((resolve) => {
setTimeout(() => resolve("Secondary API data"), 1500);
});
}
function fetchFromCache() {
return new Promise((resolve) => {
setTimeout(() => resolve("Cached data"), 500);
});
}
Promise.any([
fetchFromPrimaryAPI(),
fetchFromSecondaryAPI(),
fetchFromCache()
])
.then((data) => {
console.log("Got data from fastest available source:", data);
// Output: Got data from fastest available source: Cached data
});
// 2. Multiple authentication methods
function authenticateWithEmail(credentials) {
return new Promise((resolve, reject) => {
setTimeout(() => reject("Email auth failed"), 800);
});
}
function authenticateWithOAuth(provider) {
return new Promise((resolve) => {
setTimeout(() => resolve({ user: "John", method: "OAuth" }), 1200);
});
}
function authenticateWithToken(token) {
return new Promise((resolve, reject) => {
setTimeout(() => reject("Token invalid"), 500);
});
}
Promise.any([
authenticateWithEmail({ email: "user@example.com", password: "pass" }),
authenticateWithOAuth("google"),
authenticateWithToken("abc123")
])
.then((result) => {
console.log("Authenticated:", result);
})
.catch((error) => {
console.error("All authentication methods failed");
});
Comparison: Choosing the Right Method
Here's a quick guide for choosing the appropriate Promise method:
Promise.all():
✓ Use when: All operations must succeed
✓ Use when: You need all results together
✗ Avoid when: Partial success is acceptable
Example: Loading critical page resources
Promise.race():
✓ Use when: You need the fastest result
✓ Use when: Implementing timeouts
✗ Avoid when: You need all results
Example: Request timeout, fastest server response
Promise.allSettled():
✓ Use when: Partial success is acceptable
✓ Use when: You need to know all outcomes
✓ Use when: Failures shouldn't stop other operations
Example: Batch operations, multiple file uploads
Promise.any():
✓ Use when: Any success is sufficient
✓ Use when: You have fallback options
✗ Avoid when: You need all successes
Example: Multiple data sources, redundant servers
Combining Promise Methods
You can combine different Promise methods for advanced patterns:
// Pattern: Fetch from multiple sources, timeout each request
function fetchWithIndividualTimeouts(urls, timeout) {
const fetchPromises = urls.map(url => {
const fetchPromise = fetch(url).then(r => r.json());
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(`Timeout: ${url}`), timeout);
});
return Promise.race([fetchPromise, timeoutPromise]);
});
return Promise.allSettled(fetchPromises);
}
// Pattern: Get fastest successful response with timeout
function getFastestValidResponse(urls, timeout) {
const promises = urls.map(url => {
return Promise.race([
fetch(url).then(r => {
if (r.ok) return r.json();
throw new Error(`HTTP ${r.status}`);
}),
new Promise((_, reject) => {
setTimeout(() => reject("Timeout"), timeout);
})
]);
});
return Promise.any(promises);
}
// Usage
const apiUrls = [
"https://api1.example.com/data",
"https://api2.example.com/data",
"https://api3.example.com/data"
];
getFastestValidResponse(apiUrls, 3000)
.then(data => console.log("Got data:", data))
.catch(error => console.error("All APIs failed:", error));
Error Handling Best Practices
Proper error handling with Promise methods:
// ❌ Bad: No error handling
Promise.all([fetch(url1), fetch(url2), fetch(url3)]);
// ✅ Good: Always handle errors
Promise.all([fetch(url1), fetch(url2), fetch(url3)])
.then(responses => Promise.all(responses.map(r => r.json())))
.then(data => console.log(data))
.catch(error => console.error("Failed:", error));
// ✅ Better: Individual error handling with allSettled
const promises = [url1, url2, url3].map(url =>
fetch(url)
.then(r => r.json())
.catch(error => ({ error: error.message, url }))
);
Promise.allSettled(promises)
.then(results => {
const successful = results
.filter(r => r.status === "fulfilled" && !r.value.error)
.map(r => r.value);
const failed = results
.filter(r => r.status === "rejected" || r.value.error);
console.log(`Success: ${successful.length}, Failed: ${failed.length}`);
});
Practice Exercise:
Challenge: Create a function fetchMultipleWithRetry(urls, maxRetries) that fetches from multiple URLs, retries failed requests up to maxRetries times, and returns all results using Promise.allSettled().
Solution:
function fetchWithRetry(url, maxRetries = 3) {
function attempt(retriesLeft) {
return fetch(url)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.json();
})
.catch(error => {
if (retriesLeft <= 0) {
throw new Error(`Failed after ${maxRetries} attempts: ${error.message}`);
}
console.log(`Retrying ${url}... (${retriesLeft} attempts left)`);
return new Promise(resolve => setTimeout(resolve, 1000))
.then(() => attempt(retriesLeft - 1));
});
}
return attempt(maxRetries);
}
function fetchMultipleWithRetry(urls, maxRetries = 3) {
const fetchPromises = urls.map(url => {
return fetchWithRetry(url, maxRetries)
.then(data => ({ url, success: true, data }))
.catch(error => ({ url, success: false, error: error.message }));
});
return Promise.allSettled(fetchPromises)
.then(results => {
return results.map(result => result.value);
});
}
// Usage
const urls = [
"https://api.example.com/users",
"https://api.example.com/posts",
"https://api.example.com/comments"
];
fetchMultipleWithRetry(urls, 3)
.then(results => {
const successful = results.filter(r => r.success);
const failed = results.filter(r => !r.success);
console.log(`Successful: ${successful.length}/${urls.length}`);
console.log("Results:", results);
});
Performance Considerations
Tips for optimizing Promise performance:
// ❌ Sequential execution (slow)
async function loadDataSequential() {
const data1 = await fetch(url1);
const data2 = await fetch(url2);
const data3 = await fetch(url3);
return [data1, data2, data3];
}
// ✅ Parallel execution (fast)
async function loadDataParallel() {
return Promise.all([
fetch(url1),
fetch(url2),
fetch(url3)
]);
}
// Limit concurrent operations
function limitConcurrency(promises, limit) {
const results = [];
const executing = [];
for (const promise of promises) {
const p = Promise.resolve(promise).then(result => {
executing.splice(executing.indexOf(p), 1);
return result;
});
results.push(p);
if (executing.length >= limit) {
await Promise.race(executing);
}
executing.push(p);
}
return Promise.all(results);
}
Summary
In this lesson, you learned:
Promise.all() waits for all Promises to succeed or fails fast
Promise.race() returns the first Promise to settle (resolve or reject)
Promise.allSettled() waits for all Promises and returns all results
Promise.any() returns the first successful Promise
- How to choose the right Promise method for your use case
- Combining Promise methods for advanced patterns
- Error handling best practices for multiple Promises
- Performance optimization with parallel execution
- Practical patterns: timeouts, retries, fallbacks, batch operations
Next Up: In the next lesson, we'll master async/await - the modern syntax for writing asynchronous code that looks synchronous!