advanced
Step 14 of 20
Error Handling
JavaScript Programming
Error Handling
Robust error handling separates professional-quality code from fragile scripts that break unexpectedly. JavaScript provides the try/catch/finally mechanism for handling runtime errors, along with the ability to create custom error classes for domain-specific error types. In modern JavaScript, error handling extends to asynchronous code with async/await and Promises. Understanding how to anticipate, catch, and recover from errors makes your applications more reliable and provides better user experiences when things go wrong.
Try/Catch/Finally
// Basic try/catch
try {
const data = JSON.parse('{"valid": true}');
console.log(data.valid); // true
} catch (error) {
console.error("Parse error:", error.message);
}
// Finally — always executes
function readFile(path) {
let connection = null;
try {
connection = openConnection(path);
return connection.read();
} catch (error) {
console.error("Read failed:", error.message);
return null;
} finally {
// Cleanup — runs even if return was called
if (connection) connection.close();
console.log("Connection cleanup complete");
}
}
// Catching specific error types
try {
undefinedFunction();
} catch (error) {
if (error instanceof ReferenceError) {
console.log("Variable/function not found");
} else if (error instanceof TypeError) {
console.log("Wrong type operation");
} else if (error instanceof SyntaxError) {
console.log("Syntax issue");
} else {
throw error; // Re-throw unknown errors
}
}
Throwing Errors
// Throw built-in error types
function divide(a, b) {
if (typeof a !== "number" || typeof b !== "number") {
throw new TypeError("Both arguments must be numbers");
}
if (b === 0) {
throw new RangeError("Cannot divide by zero");
}
return a / b;
}
try {
console.log(divide(10, 0));
} catch (e) {
console.error(`${e.constructor.name}: ${e.message}`);
// "RangeError: Cannot divide by zero"
}
// Validation function
function validateEmail(email) {
if (!email) throw new Error("Email is required");
if (!email.includes("@")) throw new Error("Invalid email format");
return true;
}
Custom Error Classes
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.name = "AppError";
this.statusCode = statusCode;
}
}
class ValidationError extends AppError {
constructor(field, message) {
super(message, 400);
this.name = "ValidationError";
this.field = field;
}
}
class NotFoundError extends AppError {
constructor(resource) {
super(`${resource} not found`, 404);
this.name = "NotFoundError";
this.resource = resource;
}
}
class AuthenticationError extends AppError {
constructor() {
super("Authentication required", 401);
this.name = "AuthenticationError";
}
}
// Using custom errors
function getUser(id) {
if (!id) throw new ValidationError("id", "User ID is required");
const user = database.find(u => u.id === id);
if (!user) throw new NotFoundError("User");
return user;
}
try {
const user = getUser(null);
} catch (error) {
if (error instanceof ValidationError) {
console.log(`Validation: ${error.field} - ${error.message}`);
} else if (error instanceof NotFoundError) {
console.log(`Not found: ${error.resource}`);
} else {
console.error("Unexpected error:", error);
}
}
Async Error Handling
// With async/await
async function fetchUserData(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new AppError("Failed to fetch user", response.status);
}
return await response.json();
} catch (error) {
if (error instanceof AppError) {
console.error(`App error (${error.statusCode}): ${error.message}`);
} else {
console.error("Network error:", error.message);
}
return null;
}
}
// Global error handlers
window.addEventListener("unhandledrejection", (event) => {
console.error("Unhandled promise rejection:", event.reason);
event.preventDefault(); // Prevent default browser behavior
});
window.addEventListener("error", (event) => {
console.error("Uncaught error:", event.error);
// Log to error tracking service
});
Pro tip: Create a hierarchy of custom error classes for your application. Have a baseAppErrorclass and specific subclasses likeValidationError,NotFoundError, andAuthError. This makes error handling in your catch blocks more precise and allows middleware to handle different error types appropriately.
Key Takeaways
- Use
try/catch/finallyto handle errors gracefully;finallyalways runs for cleanup. - Throw meaningful errors with
throw new Error("message")and specific error types. - Create custom error classes extending
Errorfor domain-specific error handling. - Handle async errors with
try/catchinside async functions and.catch()on Promises. - Set up global error handlers (
unhandledrejection,errorevents) to catch any errors that slip through.