Advanced JavaScript (ES6+)

Promise Methods

13 min Lesson 15 of 40

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!

ES
Edrees Salih
23 hours ago

We are still cooking the magic in the way!