Error Handling in Async Code

📖 Concept

Async error handling requires different patterns than synchronous code because errors can occur in callbacks, Promises, or async functions at different points in time.

Promise errors: Use .catch() at the end of chains async/await errors: Use try/catch blocks Global handlers: window.onunhandledrejection and window.onerror

🏠 Real-world analogy: Async error handling is like having contingency plans for a project. If Step 3 fails (API down), you need a plan B that doesn't crash the entire project. And you need a global "fire alarm" for any unhandled emergencies.

💻 Code Example

codeTap to expand ⛶
1// try/catch in async functions
2async function loadUserData(userId) {
3 try {
4 const res = await fetch(`/api/users/${userId}`);
5 if (!res.ok) throw new Error(`HTTP ${res.status}`);
6 const user = await res.json();
7 return user;
8 } catch (error) {
9 if (error instanceof TypeError) {
10 console.error("Network error:", error.message);
11 } else {
12 console.error("API error:", error.message);
13 }
14 return null; // Graceful fallback
15 }
16}
17
18// Handling errors in Promise.all
19async function fetchAll() {
20 try {
21 const results = await Promise.all([
22 fetch("/api/1").then(r => r.json()),
23 fetch("/api/2").then(r => r.json())
24 ]);
25 return results;
26 } catch (err) {
27 // One failure = all fail with Promise.all
28 console.error("At least one request failed:", err);
29 }
30}
31
32// Better: Promise.allSettled for partial failures
33async function fetchAllSafe() {
34 const results = await Promise.allSettled([
35 fetch("/api/1").then(r => r.json()),
36 fetch("/api/2").then(r => r.json())
37 ]);
38 const successes = results.filter(r => r.status === "fulfilled").map(r => r.value);
39 const failures = results.filter(r => r.status === "rejected").map(r => r.reason);
40 return { successes, failures };
41}
42
43// Error boundary pattern
44async function withErrorBoundary(fn, fallback) {
45 try {
46 return await fn();
47 } catch (error) {
48 console.error("Caught:", error);
49 return typeof fallback === "function" ? fallback(error) : fallback;
50 }
51}
52const data = await withErrorBoundary(
53 () => fetch("/api/data").then(r => r.json()),
54 [] // Fallback value
55);
56
57// Global error handlers
58window.addEventListener("unhandledrejection", (event) => {
59 console.error("Unhandled rejection:", event.reason);
60 event.preventDefault(); // Prevent default console error
61});

🏋️ Practice Exercise

Mini Exercise:

  1. Write an error boundary wrapper for async operations with retry
  2. Implement graceful degradation: try primary API, fall back to cache
  3. Create a centralized error logging system for async operations
  4. Build a circuit breaker pattern that stops calling a failing service

⚠️ Common Mistakes

  • Not catching errors in async functions — unhandled rejections crash Node.js and show warnings in browsers

  • Using try/catch around a function that returns a Promise without await — the catch won't work!

  • Not distinguishing network errors from HTTP errors — fetch only throws on network failures

  • Catching and silently swallowing errors with an empty catch block — always log or re-throw

  • Not cleaning up resources (event listeners, intervals) on error — use finally for cleanup

💼 Interview Questions

🎤 Mock Interview

Mock interview is powered by AI for Error Handling in Async Code. Login to unlock this feature.