SOLUTION: Challenge 1 - Async Function with Error Handling =========================================================== Challenge: Write an async function that fetches data from a URL, parses JSON, and handles network errors separately from parse errors. Return the data or throw an appropriate error. --- SOLUTION: async function fetchJSON(url: string): Promise { try { // Fetch the URL const response = await fetch(url); // Check HTTP status if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } // Parse JSON const data: T = await response.json(); return data; } catch (error) { // Distinguish between network errors and parse errors if (error instanceof TypeError) { // Network error (fetch failed) throw new Error(`Network error: ${error.message}`); } else if (error instanceof SyntaxError) { // JSON parse error throw new Error(`Invalid JSON: ${error.message}`); } else if (error instanceof Error) { // Our custom errors (HTTP status) throw error; } else { // Unknown error throw new Error(`Unknown error: ${String(error)}`); } } } // Test it interface User { id: number; name: string; } // Good case try { const user = await fetchJSON("/api/user/1"); console.log("User:", user); } catch (error) { console.error(error.message); } // Network error (URL doesn't exist, invalid domain) try { const data = await fetchJSON("/invalid-url"); } catch (error) { console.error(error.message); // "Network error: ..." } // Bad JSON response try { const data = await fetchJSON("/bad-json"); } catch (error) { console.error(error.message); // "Invalid JSON: ..." } --- EXPLANATION: ERROR TYPES: 1. TypeError — Network/fetch failure - URL unreachable - CORS blocked - Connection timeout Indicates a network layer problem. 2. SyntaxError — JSON parsing failure - Response body isn't valid JSON - Got HTML error page instead of JSON Indicates server returned garbage. 3. Custom Error — HTTP status codes - 404, 500, etc. - We throw these explicitly after checking response.ok Indicates the request reached the server but failed. 4. Unknown — Unexpected errors Should rarely happen but handle gracefully. TYPING: The generic lets you specify the expected return type: const user = await fetchJSON("/api/user"); // user is typed as User, not unknown BEST PRACTICE: - Always check response.ok before parsing JSON - Catch network errors (TypeError) separately - Re-throw or create specific error types so callers know what went wrong --- ADVANCED: Custom error class For more control, define custom error types: class NetworkError extends Error { constructor(message: string) { super(message); this.name = "NetworkError"; } } class ParseError extends Error { constructor(message: string) { super(message); this.name = "ParseError"; } } class HTTPError extends Error { constructor(public status: number, message: string) { super(message); this.name = "HTTPError"; } } async function fetchJSON(url: string): Promise { try { const response = await fetch(url); if (!response.ok) { throw new HTTPError(response.status, response.statusText); } const data: T = await response.json(); return data; } catch (error) { if (error instanceof HTTPError) { throw error; // Re-throw as-is } else if (error instanceof TypeError) { throw new NetworkError(error.message); } else if (error instanceof SyntaxError) { throw new ParseError(error.message); } throw error; } } // Callers can now distinguish: try { const user = await fetchJSON("/api/user"); } catch (error) { if (error instanceof NetworkError) { console.error("Connection problem, try again later"); } else if (error instanceof ParseError) { console.error("Server returned bad data"); } else if (error instanceof HTTPError) { console.error(`Server error: ${error.status}`); } } This pattern is more maintainable for large applications.