Union types in error handling allow functions to return either a success value or an error type. Example: type Result<T> = { success: true; data: T; } | { success: false; error: Error; }. This pattern enables type-safe error handling without throwing exceptions: function process(): Result<string> { try { return { success: true, data: 'processed' }; } catch (e) { return { success: false, error: e instanceof Error ? e : new Error(String(e)) }; } }
Error boundaries are implemented using class components with error handling lifecycle methods. Example: class ErrorBoundary extends React.Component<Props, State> { static getDerivedStateFromError(error: Error): State { return { hasError: true, error }; } componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { logErrorToService(error, errorInfo); } } This catches and handles errors in child components.
Source maps enable debugging of TypeScript code directly, even though the browser runs compiled JavaScript. They map positions in the compiled code to the original TypeScript. Enable with 'sourceMap: true' in tsconfig.json. This allows setting breakpoints in TypeScript files and seeing TypeScript variables during debugging.
Memory leaks can be debugged using: 1) Chrome DevTools Memory panel, 2) Heap snapshots, 3) Memory allocation timeline, 4) Node.js --inspect flag. Example: Taking heap snapshots: const heapSnapshot = require('heapdump'); heapSnapshot.writeSnapshot(`${Date.now()}.heapsnapshot`); Then analyze using Chrome DevTools.
Custom error types can be created by extending the Error class. Example: class ValidationError extends Error { constructor(message: string) { super(message); this.name = 'ValidationError'; Object.setPrototypeOf(this, ValidationError.prototype); } } This allows for type-safe error handling and custom error properties.
Async errors can be handled using try-catch with async/await or .catch() with Promises. Example: async function handleAsync() { try { await riskyOperation(); } catch (error) { if (error instanceof NetworkError) { // Handle network errors } else if (error instanceof ValidationError) { // Handle validation errors } throw error; // Re-throw unhandled errors } }
Type assertions should be used carefully in error handling to maintain type safety. Example: function handleError(error: unknown) { if (error instanceof Error) { const customError = error as CustomError; // Only assert after instanceof check if (customError.code) { // Handle custom error } } }
Error logging can be implemented using a centralized error logging service. Example: class ErrorLogger { static log(error: Error, context?: object) { const errorLog = { timestamp: new Date(), name: error.name, message: error.message, stack: error.stack, context }; // Send to logging service or store locally console.error(errorLog); } }
throw is used for synchronous error handling and immediately stops execution, while reject is used in Promises for asynchronous error handling. Example: function sync() { throw new Error('Sync error'); } async function async() { return Promise.reject(new Error('Async error')); } throw creates an exception, reject creates a rejected Promise.
Conditional types help create type-safe error handling patterns. Example: type ErrorResponse<T> = T extends Error ? { error: T; data: null; } : { error: null; data: T; }; function handleResult<T>(result: T): ErrorResponse<T> { return result instanceof Error ? { error: result, data: null } : { error: null, data: result }; }
Promise rejections can be handled using .catch(), try-catch with async/await, or global handlers. Example: window.onunhandledrejection = (event: PromiseRejectionEvent) => { console.error('Unhandled promise rejection:', event.reason); event.preventDefault(); }; This ensures all rejected Promises are handled.
Type guards help narrow down error types for proper handling. Example: function isNetworkError(error: unknown): error is NetworkError { return error instanceof NetworkError; } try { // risky operation } catch (error) { if (isNetworkError(error)) { console.log(error.statusCode); // TypeScript knows error is NetworkError } }
Best practices include: 1) Use custom error classes for specific error types, 2) Implement type-safe error handling with union types, 3) Always check error types before using their properties, 4) Use async/await with try-catch for async operations, 5) Implement proper error logging and monitoring, 6) Use error boundaries in React applications, 7) Avoid using 'any' for error types, prefer 'unknown'.
TypeScript tests can be debugged using: 1) Jest's debugger with ts-jest, 2) VS Code's debug configuration for tests, 3) Chrome DevTools with karma. Example launch.json: { 'type': 'node', 'request': 'launch', 'name': 'Debug Tests', 'program': '${workspaceFolder}/node_modules/jest/bin/jest', 'args': ['--runInBand'] }
The 'unknown' type is safer than 'any' for error handling as it requires type checking before use. Example: function handleError(error: unknown) { if (error instanceof Error) { console.log(error.message); } else if (typeof error === 'string') { console.log(error); } else { console.log('Unknown error occurred'); } }
TypeScript debugging tools include: 1) Source maps for debugging compiled code, 2) VS Code's built-in debugger, 3) Chrome DevTools with source maps, 4) debugger statement, 5) console methods (log, warn, error, trace), 6) TypeScript compiler flags like --noEmitOnError, 7) Jest debugger for testing. Configuration in launch.json: { 'type': 'node', 'request': 'launch', 'sourceMaps': true }
Retry logic can be implemented using recursive functions or libraries with proper error handling. Example: async function retryOperation<T>(operation: () => Promise<T>, maxRetries: number): Promise<T> { try { return await operation(); } catch (error) { if (maxRetries > 0) { await delay(1000); return retryOperation(operation, maxRetries - 1); } throw error; } }
Discriminated unions provide type-safe error handling by using a common property to discriminate between success and error states. Example: type Result<T> = { kind: 'success'; value: T; } | { kind: 'error'; error: Error; }; This enables exhaustive checking of all possible states.
Circuit breakers prevent cascading failures by stopping operations after too many errors. Example: class CircuitBreaker { private failures = 0; private readonly threshold = 5; async execute<T>(operation: () => Promise<T>): Promise<T> { if (this.failures >= this.threshold) { throw new Error('Circuit breaker open'); } try { const result = await operation(); this.failures = 0; return result; } catch (error) { this.failures++; throw error; } } }