Use try/catch for synchronous code and .catch() or try/catch with async/await for promises. Create custom error types for different failure modes.
Proper error handling is crucial for building robust applications that fail gracefully.
try/catch wraps code that might throw: try { riskyOperation() } catch (error) { handleError(error) }. The finally block runs regardless of success or failure—useful for cleanup.
For promises, use .catch(): fetchData().then(process).catch(handleError). With async/await, wrap in try/catch. Unhandled promise rejections can crash Node.js or go silently wrong in browsers.
Always handle errors at appropriate levels. Low-level functions might throw or return error information. Higher levels decide how to respond—retry, show user message, log and continue, or abort. Don't catch errors just to swallow them.
Create custom error classes for different failure types: class ValidationError extends Error { constructor(message, field) { super(message); this.field = field; } }. This lets callers handle different errors differently.
In async code, consider whether to throw or return { data, error } objects. The latter (like Go's style) makes error handling explicit at call sites but is more verbose. Both approaches work—be consistent.
For critical applications, use error boundaries in React, global error handlers (window.onerror, process.on('unhandledRejection')), and error monitoring services to catch what slips through.
Use try/catch for synchronous code and .catch() or try/catch with async/await for promises. Create custom error types for different failure modes.
Join our network of elite AI-native engineers.