Promises Advanced: Combinators and Patterns
Promises Advanced: Combinators and Patterns
Promise.all, allSettled, race, any, concurrency pooling, and retry with exponential backoff.
The Four Olympic Team Relay Events
Promise combinators coordinate multiple concurrent operations. Choosing the right combinator prevents silent drops, improves user experience, and optimizes throughput.
1. Promise.all([p1, p2])
Fails fast: rejects immediately if ANY promise rejects. Resolves with an array of values when ALL promises fulfill. Best for dependent, all-or-nothing operations.
2. Promise.allSettled([p1, p2])
Never rejects early. Waits for all promises to finish and returns an array of outcome objects: `{ status: "fulfilled", value }` or `{ status: "rejected", reason }`.
3. Promise.race([p1, p2])
Settles as soon as the FIRST promise settles (whether fulfilled OR rejected). Ideal for implementing network timeouts.
4. Promise.any([p1, p2])
Resolves as soon as the FIRST promise fulfills (ignores rejections). Only rejects if ALL promises reject, throwing an `AggregateError`.
Retry Pattern with Exponential Backoff
Automatically retries failed network calls with progressively longer delays (2N × \text{base} + \text{jitter}) to prevent overloading recovering servers.
Building a network timeout with Promise.race and retry with exponential backoff:
// 1. Timeout Pattern with Promise.race
function withTimeout(promise, ms) {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error(`Request timed out after ${ms}ms`)), ms)
);
return Promise.race([promise, timeout]);
}
// 2. Retry with Exponential Backoff
async function retryWithBackoff(fn, retries = 3, delay = 50) {
try {
return await fn();
} catch (err) {
if (retries <= 0) throw err;
console.log(`Attempt failed, retrying in ${delay}ms...`);
await new Promise(res => setTimeout(res, delay));
return retryWithBackoff(fn, retries - 1, delay * 2);
}
}
// Simulating unstable API
let attempt = 0;
const unstableApi = () => new Promise((res, reject) => {
attempt++;
attempt < 3 ? reject(new Error("503 Service Unavailable")) : res("Success on attempt 3!");
});
retryWithBackoff(unstableApi).then(msg => console.log("Final:", msg));
Attempt failed, retrying in 50ms...
Attempt failed, retrying in 100ms...
Final: Success on attempt 3!
Using Promise.all for Independent Operations
The Risk: Using `Promise.all` to upload 10 user avatar images: if image #9 fails due to a network blip, the entire batch rejects, losing the other 9 successful uploads.
The Fix: Use `Promise.allSettled()` so partial successes can be kept and only failed items retried.
- ✓ `Promise.all`: All succeed or first to reject (fast-fail).
- ✓ `Promise.allSettled`: Waits for all; returns array of `{ status, value/reason }`.
- ✓ `Promise.race`: First to settle (either fulfill OR reject). Perfect for timeouts.
- ✓ `Promise.any`: First to fulfill; rejects only if all reject (`AggregateError`).
- ✓ Use exponential backoff with jitter for resilient network retries.