Types of Errors
JavaScript has several built-in error types, each indicating a different kind of problem.
// SyntaxError - Code can't be parsed
// const x = ; // SyntaxError: Unexpected token ';'
// ReferenceError - Variable doesn't exist
// console.log(undefinedVar); // ReferenceError
// TypeError - Wrong type for operation
// null.toString(); // TypeError: Cannot read properties of null
// RangeError - Number out of valid range
// new Array(-1); // RangeError: Invalid array length
// URIError - Malformed URI
// decodeURIComponent('%'); // URIError
// Error object properties
try {
throw new Error("Something went wrong");
} catch (error) {
console.log(error.name); // "Error"
console.log(error.message); // "Something went wrong"
console.log(error.stack); // Stack trace (non-standard but widely supported)
}
All error types inherit from Error: Error → TypeError, ReferenceError, SyntaxError, RangeError, URIError, EvalError
Try...Catch...Finally
The try/catch statement handles exceptions gracefully without crashing your program.
// Basic try...catch
try {
// Code that might throw an error
const data = JSON.parse("invalid json");
} catch (error) {
// Handle the error
console.log("Parse error:", error.message);
}
// Program continues...
// try...catch...finally
function fetchData() {
console.log("Opening connection...");
try {
// Simulating an error
throw new Error("Network error");
} catch (error) {
console.log("Error:", error.message);
} finally {
// Always runs, even if there's an error
console.log("Closing connection...");
}
}
fetchData();
// "Opening connection..."
// "Error: Network error"
// "Closing connection..."
// finally runs even with return
function getValue() {
try {
return 1;
} finally {
console.log("Cleanup"); // Still runs!
}
}
console.log(getValue());
// "Cleanup"
// 1
// Catch without variable (ES2019)
try {
JSON.parse("invalid");
} catch {
console.log("Parse failed");
}
// Nested try...catch
function processData(data) {
try {
try {
const parsed = JSON.parse(data);
return parsed.value.toUpperCase();
} catch (parseError) {
console.log("Inner catch:", parseError.message);
throw new Error("Data processing failed");
}
} catch (outerError) {
console.log("Outer catch:", outerError.message);
return null;
}
}
console.log(processData("invalid"));
// "Inner catch: ..."
// "Outer catch: Data processing failed"
// null
Throwing Errors
Use throw to create your own errors when something goes wrong.
// Throwing an Error object
function divide(a, b) {
if (b === 0) {
throw new Error("Cannot divide by zero");
}
return a / b;
}
try {
console.log(divide(10, 0));
} catch (error) {
console.log(error.message); // "Cannot divide by zero"
}
// Throwing specific error types
function setAge(age) {
if (typeof age !== "number") {
throw new TypeError("Age must be a number");
}
if (age < 0 || age > 150) {
throw new RangeError("Age must be between 0 and 150");
}
return age;
}
// Validation with errors
function validateUser(user) {
if (!user) {
throw new Error("User is required");
}
if (!user.name) {
throw new Error("User name is required");
}
if (!user.email) {
throw new Error("User email is required");
}
if (!user.email.includes("@")) {
throw new Error("Invalid email format");
}
return true;
}
try {
validateUser({ name: "John" });
} catch (error) {
console.log("Validation failed:", error.message);
// "Validation failed: User email is required"
}
// Re-throwing errors
function processFile(filename) {
try {
// Simulate file operation
if (!filename.endsWith(".txt")) {
throw new Error("Only .txt files supported");
}
// Process file...
} catch (error) {
console.log(`Error processing ${filename}`);
throw error; // Re-throw for caller to handle
}
}
Custom Error Classes
Create your own error types for more specific error handling.
// Custom error class
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = "ValidationError";
this.field = field;
}
}
class NetworkError extends Error {
constructor(message, statusCode) {
super(message);
this.name = "NetworkError";
this.statusCode = statusCode;
}
}
class AuthenticationError extends Error {
constructor(message = "Authentication required") {
super(message);
this.name = "AuthenticationError";
}
}
// Using custom errors
function validateEmail(email) {
if (!email) {
throw new ValidationError("Email is required", "email");
}
if (!email.includes("@")) {
throw new ValidationError("Invalid email format", "email");
}
}
// Handling different error types
function handleRequest(user) {
try {
if (!user.token) {
throw new AuthenticationError();
}
validateEmail(user.email);
// Make request...
} catch (error) {
if (error instanceof AuthenticationError) {
console.log("Please log in first");
// Redirect to login...
} else if (error instanceof ValidationError) {
console.log(`Invalid ${error.field}: ${error.message}`);
// Show form error...
} else if (error instanceof NetworkError) {
console.log(`Network error (${error.statusCode}): ${error.message}`);
// Retry logic...
} else {
// Unknown error - re-throw or log
console.log("Unexpected error:", error);
throw error;
}
}
}
// HTTP-style errors
class HttpError extends Error {
constructor(status, message) {
super(message);
this.name = "HttpError";
this.status = status;
}
static badRequest(message = "Bad Request") {
return new HttpError(400, message);
}
static unauthorized(message = "Unauthorized") {
return new HttpError(401, message);
}
static notFound(message = "Not Found") {
return new HttpError(404, message);
}
static internal(message = "Internal Server Error") {
return new HttpError(500, message);
}
}
// Usage
throw HttpError.notFound("User not found");
Error Handling Patterns
Common patterns for handling errors effectively.
// Pattern 1: Error-first callbacks (Node.js style)
function readFileCallback(filename, callback) {
setTimeout(() => {
if (!filename) {
callback(new Error("Filename required"), null);
} else {
callback(null, "File contents here");
}
}, 100);
}
readFileCallback("test.txt", (error, data) => {
if (error) {
console.log("Error:", error.message);
return;
}
console.log("Data:", data);
});
// Pattern 2: Result objects (no exceptions)
function safeDivide(a, b) {
if (b === 0) {
return { success: false, error: "Cannot divide by zero" };
}
return { success: true, value: a / b };
}
const result = safeDivide(10, 0);
if (result.success) {
console.log("Result:", result.value);
} else {
console.log("Error:", result.error);
}
// Pattern 3: Optional chaining for safe access
const user = {
profile: null
};
// Old way
const city = user && user.profile && user.profile.address && user.profile.address.city;
// Modern way - no errors even if null
const cityModern = user?.profile?.address?.city;
console.log(cityModern); // undefined (not an error)
// Pattern 4: Nullish coalescing for defaults
const name = user?.name ?? "Anonymous";
console.log(name); // "Anonymous"
// Pattern 5: Wrapper functions for try/catch
function tryCatch(fn) {
return function(...args) {
try {
return { success: true, value: fn(...args) };
} catch (error) {
return { success: false, error };
}
};
}
const safeParseJSON = tryCatch(JSON.parse);
console.log(safeParseJSON('{"valid": true}'));
// { success: true, value: { valid: true } }
console.log(safeParseJSON('invalid'));
// { success: false, error: SyntaxError }
// Pattern 6: Async error boundary
async function withErrorBoundary(fn) {
try {
return await fn();
} catch (error) {
console.error("Error caught:", error.message);
// Log to service, show notification, etc.
return null;
}
}
Debugging Tips
Tools and techniques for finding and fixing errors.
// Console methods for debugging
console.log("Basic log");
console.error("Error log"); // Red in console
console.warn("Warning log"); // Yellow in console
console.info("Info log");
console.debug("Debug log");
// Grouping logs
console.group("User Details");
console.log("Name: John");
console.log("Age: 30");
console.groupEnd();
// Table format
const users = [
{ name: "Alice", age: 25 },
{ name: "Bob", age: 30 }
];
console.table(users);
// Timing
console.time("operation");
// ... some code ...
console.timeEnd("operation"); // "operation: 123ms"
// Stack trace
console.trace("Trace point");
// Assert (logs only if condition is false)
console.assert(1 === 2, "Math is broken!");
// Debugger statement - pauses execution
function buggyFunction(x) {
debugger; // Opens browser dev tools here
return x * 2;
}
// Inspecting error stack
function level3() {
throw new Error("Deep error");
}
function level2() { level3(); }
function level1() { level2(); }
try {
level1();
} catch (error) {
console.log(error.stack);
// Error: Deep error
// at level3 (...)
// at level2 (...)
// at level1 (...)
}
// Error cause (ES2022)
try {
try {
throw new Error("Database connection failed");
} catch (dbError) {
throw new Error("User fetch failed", { cause: dbError });
}
} catch (error) {
console.log(error.message); // "User fetch failed"
console.log(error.cause.message); // "Database connection failed"
}
Best Practices
// 1. Be specific about what you catch
// Bad - catches everything
try {
doSomething();
} catch (e) {
console.log("Something went wrong");
}
// Good - handle specific errors
try {
doSomething();
} catch (error) {
if (error instanceof NetworkError) {
// Handle network issues
} else if (error instanceof ValidationError) {
// Handle validation
} else {
throw error; // Re-throw unknown errors
}
}
// 2. Don't swallow errors silently
// Bad
try {
riskyOperation();
} catch (e) {
// Empty catch - error disappears!
}
// Good - at minimum, log the error
try {
riskyOperation();
} catch (error) {
console.error("Operation failed:", error);
// Consider: report to error tracking service
}
// 3. Fail fast with validation
function processOrder(order) {
// Validate early
if (!order) throw new Error("Order is required");
if (!order.items?.length) throw new Error("Order must have items");
if (order.total < 0) throw new Error("Invalid order total");
// Now process with confidence
return processValidOrder(order);
}
// 4. Provide context in error messages
// Bad
throw new Error("Invalid input");
// Good
throw new Error(`Invalid user ID: ${userId}. Expected positive integer.`);
// 5. Clean up resources in finally
function processWithResource() {
const connection = openConnection();
try {
return doWork(connection);
} finally {
connection.close(); // Always runs
}
}
// 6. Use error boundaries in UI frameworks
class ErrorBoundary {
constructor(fallback) {
this.fallback = fallback;
this.error = null;
}
wrap(fn) {
try {
return fn();
} catch (error) {
this.error = error;
return this.fallback(error);
}
}
}
- Validate inputs before processing
- Use descriptive error messages
- Log errors with context (user, request, timestamp)
- Don't expose sensitive info in production errors
- Have a global error handler as fallback
- Test error scenarios, not just happy paths
Advanced Custom Error Patterns
Build a comprehensive error handling system with error codes, metadata, and proper serialization.
// Base application error with rich metadata
class AppError extends Error {
constructor(message, options = {}) {
super(message, { cause: options.cause });
this.name = this.constructor.name;
this.code = options.code || "UNKNOWN_ERROR";
this.statusCode = options.statusCode || 500;
this.isOperational = options.isOperational ?? true;
this.metadata = options.metadata || {};
this.timestamp = new Date().toISOString();
Error.captureStackTrace?.(this, this.constructor);
}
toJSON() {
return {
name: this.name,
message: this.message,
code: this.code,
statusCode: this.statusCode,
metadata: this.metadata,
timestamp: this.timestamp,
...(process.env.NODE_ENV !== "production" && { stack: this.stack })
};
}
}
// Specific error types
class ValidationError extends AppError {
constructor(message, field, options = {}) {
super(message, {
...options,
code: options.code || "VALIDATION_ERROR",
statusCode: 400
});
this.field = field;
this.metadata.field = field;
}
}
class NotFoundError extends AppError {
constructor(resource, id, options = {}) {
super(`${resource} with id '${id}' not found`, {
...options,
code: "NOT_FOUND",
statusCode: 404,
metadata: { resource, id }
});
}
}
class AuthenticationError extends AppError {
constructor(message = "Authentication required", options = {}) {
super(message, {
...options,
code: "UNAUTHENTICATED",
statusCode: 401
});
}
}
class AuthorizationError extends AppError {
constructor(message = "Access denied", options = {}) {
super(message, {
...options,
code: "FORBIDDEN",
statusCode: 403
});
}
}
// Usage
try {
throw new NotFoundError("User", "abc123");
} catch (error) {
console.log(error.toJSON());
// { name: "NotFoundError", message: "User with id 'abc123' not found",
// code: "NOT_FOUND", statusCode: 404, metadata: { resource: "User", id: "abc123" }, ... }
}
// Error codes enum
const ErrorCodes = Object.freeze({
VALIDATION: "VALIDATION_ERROR",
NOT_FOUND: "NOT_FOUND",
DUPLICATE: "DUPLICATE_ENTRY",
RATE_LIMIT: "RATE_LIMIT_EXCEEDED",
NETWORK: "NETWORK_ERROR",
TIMEOUT: "REQUEST_TIMEOUT"
});
Error Boundaries and Recovery
Implement error boundaries to prevent cascading failures and enable graceful recovery.
// Generic error boundary wrapper
function createErrorBoundary(options = {}) {
const {
fallback,
onError,
retries = 0,
retryDelay = 1000
} = options;
return async function boundary(fn) {
let lastError;
for (let attempt = 0; attempt <= retries; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error;
onError?.(error, attempt);
if (attempt < retries) {
await new Promise(r => setTimeout(r, retryDelay * (attempt + 1)));
}
}
}
if (fallback) {
return typeof fallback === "function" ? fallback(lastError) : fallback;
}
throw lastError;
};
}
// Usage
const safeFetch = createErrorBoundary({
retries: 3,
retryDelay: 1000,
onError: (err, attempt) => console.log(`Attempt ${attempt + 1} failed:`, err.message),
fallback: (err) => ({ error: true, message: err.message, data: null })
});
const result = await safeFetch(async () => {
const response = await fetch("/api/data");
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
});
// Circuit breaker pattern
class CircuitBreaker {
constructor(options = {}) {
this.failureThreshold = options.failureThreshold || 5;
this.resetTimeout = options.resetTimeout || 30000;
this.state = "CLOSED"; // CLOSED, OPEN, HALF_OPEN
this.failures = 0;
this.lastFailure = null;
}
async execute(fn) {
if (this.state === "OPEN") {
if (Date.now() - this.lastFailure > this.resetTimeout) {
this.state = "HALF_OPEN";
} else {
throw new Error("Circuit breaker is OPEN");
}
}
try {
const result = await fn();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
onSuccess() {
this.failures = 0;
this.state = "CLOSED";
}
onFailure() {
this.failures++;
this.lastFailure = Date.now();
if (this.failures >= this.failureThreshold) {
this.state = "OPEN";
}
}
}
// Global unhandled error handlers
window.addEventListener("error", (event) => {
console.error("Uncaught error:", event.error);
// Report to error tracking service
reportError(event.error);
});
window.addEventListener("unhandledrejection", (event) => {
console.error("Unhandled promise rejection:", event.reason);
reportError(event.reason);
event.preventDefault(); // Prevent default console error
});
// Node.js equivalents
process.on("uncaughtException", (error) => {
console.error("Uncaught exception:", error);
// Graceful shutdown
process.exit(1);
});
process.on("unhandledRejection", (reason, promise) => {
console.error("Unhandled rejection at:", promise, "reason:", reason);
});
Error Logging Strategies
Implement structured logging for effective debugging and monitoring in production.
// Structured logger
class Logger {
static levels = { DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3 };
constructor(options = {}) {
this.minLevel = Logger.levels[options.level || "INFO"];
this.context = options.context || {};
}
#log(level, message, data = {}) {
if (Logger.levels[level] < this.minLevel) return;
const entry = {
timestamp: new Date().toISOString(),
level,
message,
...this.context,
...data
};
const formatted = JSON.stringify(entry);
switch (level) {
case "ERROR": console.error(formatted); break;
case "WARN": console.warn(formatted); break;
default: console.log(formatted);
}
return entry;
}
debug(msg, data) { return this.#log("DEBUG", msg, data); }
info(msg, data) { return this.#log("INFO", msg, data); }
warn(msg, data) { return this.#log("WARN", msg, data); }
error(msg, data) { return this.#log("ERROR", msg, data); }
child(context) {
return new Logger({
level: Object.keys(Logger.levels)[this.minLevel],
context: { ...this.context, ...context }
});
}
}
// Usage
const logger = new Logger({
level: "DEBUG",
context: { service: "user-api", version: "1.0.0" }
});
const requestLogger = logger.child({ requestId: "abc123" });
try {
throw new ValidationError("Email is invalid", "email");
} catch (error) {
requestLogger.error("Validation failed", {
error: {
name: error.name,
message: error.message,
code: error.code,
field: error.field,
stack: error.stack
},
userId: "user123",
action: "createUser"
});
}
// Error tracking service integration
class ErrorTracker {
constructor(apiKey, options = {}) {
this.apiKey = apiKey;
this.endpoint = options.endpoint || "/api/errors";
this.environment = options.environment || "development";
}
capture(error, context = {}) {
const payload = {
name: error.name,
message: error.message,
stack: error.stack,
code: error.code,
timestamp: new Date().toISOString(),
environment: this.environment,
url: window?.location?.href,
userAgent: navigator?.userAgent,
...context
};
// Send async, don't block
this.#send(payload).catch(console.error);
}
async #send(payload) {
await fetch(this.endpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": this.apiKey
},
body: JSON.stringify(payload)
});
}
}
// Initialize error tracking
const errorTracker = new ErrorTracker("your-api-key", {
environment: process.env.NODE_ENV
});
// Wrap functions with automatic error tracking
function withErrorTracking(fn, context = {}) {
return async (...args) => {
try {
return await fn(...args);
} catch (error) {
errorTracker.capture(error, {
...context,
functionName: fn.name,
arguments: args
});
throw error;
}
};
}
Production Error Handling
- Use error tracking: Sentry, Bugsnag, or LogRocket
- Structured logging: JSON format for parsing/querying
- Correlation IDs: Track errors across services
- Sanitize: Never log passwords, tokens, PII
- Alert: Set up alerts for error spikes
Summary
try...catch
Wrap risky code, handle errors gracefully
finally
Always runs for cleanup operations
throw
Create and throw your own errors
Custom Errors
Extend Error for specific error types
Error Properties
name, message, stack, cause
Best Practice
Be specific, don't swallow, provide context