SOLUTION: Challenge 2 - Concurrent Requests ============================================= Challenge: Write an async function that fetches data from 3 different URLs concurrently using Promise.all(). Handle the case where one fails and all fail. --- SOLUTION: interface ApiData { users: any[]; posts: any[]; comments: any[]; } async function fetchConcurrent(): Promise { try { // All three fetches run concurrently (start at same time) const [usersResponse, postsResponse, commentsResponse] = await Promise.all([ fetch("/api/users"), fetch("/api/posts"), fetch("/api/comments") ]); // Check all responses are OK if (!usersResponse.ok) { throw new Error(`Users API: HTTP ${usersResponse.status}`); } if (!postsResponse.ok) { throw new Error(`Posts API: HTTP ${postsResponse.status}`); } if (!commentsResponse.ok) { throw new Error(`Comments API: HTTP ${commentsResponse.status}`); } // Parse all responses concurrently const [users, posts, comments] = await Promise.all([ usersResponse.json(), postsResponse.json(), commentsResponse.json() ]); return { users, posts, comments }; } catch (error) { if (error instanceof Error) { console.error("Failed to fetch data:", error.message); } throw error; } } // Test it try { const data = await fetchConcurrent(); console.log("All data fetched:", data); } catch (error) { console.error("One or more requests failed"); } --- UNDERSTANDING Promise.all(): CONCURRENT vs SEQUENTIAL: Concurrent (Promise.all): Start all 3 fetches at once. If all 3 URLs take 1 second each, total time = 1 second. fetch(url1) ─┐ fetch(url2) ─┼─→ all start together fetch(url3) ─┘ Total: ~1s Sequential (await in a loop): Fetch 1, wait. Then fetch 2, wait. Then fetch 3, wait. Total = 3 seconds. fetch(url1) ──→ wait ──→ fetch(url2) ──→ wait ──→ fetch(url3) Total: ~3s ALWAYS USE Promise.all() FOR INDEPENDENT REQUESTS. FAILURE BEHAVIOR: Promise.all() fails FAST: if ANY promise rejects, the whole thing rejects immediately. The other promises might still be pending — they don't cancel (they'll finish in the background, but you won't see the results). --- ADVANCED: Partial failure with allSettled If you want to continue even if one request fails: async function fetchConcurrentResilent(): Promise { const results = await Promise.allSettled([ fetch("/api/users").then(r => r.json()), fetch("/api/posts").then(r => r.json()), fetch("/api/comments").then(r => r.json()) ]); // Each result is either { status: "fulfilled", value: ... } // or { status: "rejected", reason: ... } const [usersResult, postsResult, commentsResult] = results; if (usersResult.status === "fulfilled" && postsResult.status === "fulfilled" && commentsResult.status === "fulfilled") { return { users: usersResult.value, posts: postsResult.value, comments: commentsResult.value }; } // At least one failed — decide how to handle if (usersResult.status === "rejected") { console.error("Users request failed:", usersResult.reason); } if (postsResult.status === "rejected") { console.error("Posts request failed:", postsResult.reason); } if (commentsResult.status === "rejected") { console.error("Comments request failed:", commentsResult.reason); } throw new Error("One or more requests failed"); } This is useful when you want to: - Log individual failures - Retry failed requests separately - Use partial data (e.g., have users and posts but not comments) --- OTHER CONCURRENT UTILITIES: Promise.race() — First to settle wins const fastest = await Promise.race([ fetch("/api/fast"), fetch("/api/slow") ]); Returns whichever resolves/rejects first. Good for timeouts. Promise.any() — First to succeed wins const firstSuccess = await Promise.any([ fetch("/api/primary"), fetch("/api/fallback1"), fetch("/api/fallback2") ]); Ignores rejections until all fail. Good for failover strategies. --- BEST PRACTICES: 1. Use Promise.all() for parallel independent operations. 2. Check response.ok before parsing JSON. 3. Use Promise.allSettled() if you want to handle partial failures. 4. Always catch errors and log what failed (helps debugging). 5. Consider timeouts if third-party APIs might hang.