Error Handling in JavaScript
Proper error handling makes your code robust and easier to debug. JavaScript provides several mechanisms for handling errors.
Try-Catch-Finally
The fundamental error handling structure:
try {
// Code that might throw an error
const result = riskyOperation();
} catch (error) {
// Handle the error
console.log("Error occurred:", error.message);
} finally {
// Runs regardless of success or error
console.log("Cleanup code");
}
Basic Example
try {
const data = JSON.parse("invalid json");
} catch (error) {
console.log("Failed to parse JSON:", error.message);
}
Accessing Error Information
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); // Full stack trace
}
Error Types
JavaScript has several built-in error types:
SyntaxError
Occurs when code has invalid syntax:
// This would cause a SyntaxError at parse time
// const x = ;
ReferenceError
Accessing undefined variables:
try {
console.log(undefinedVariable);
} catch (error) {
console.log(error instanceof ReferenceError); // true
}
TypeError
Wrong type or invalid operation:
try {
const x = null;
x.property; // TypeError: Cannot read property 'property' of null
} catch (error) {
console.log(error instanceof TypeError); // true
}
RangeError
Value out of valid range:
try {
const arr = new Array(-1); // RangeError
} catch (error) {
console.log(error instanceof RangeError); // true
}
Throwing Errors
Throw Statement
function validateAge(age) {
if (age < 0) {
throw new Error("Age cannot be negative");
}
return age;
}
try {
validateAge(-5);
} catch (error) {
console.log(error.message); // "Age cannot be negative"
}
Throwing Different Types
function processData(data) {
if (!data) {
throw new TypeError("Data is required");
}
if (data.length === 0) {
throw new RangeError("Data cannot be empty");
}
return data;
}
Custom Errors
Create custom error classes for specific situations:
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
}
}
try {
throw new ValidationError("Invalid email format");
} catch (error) {
if (error instanceof ValidationError) {
console.log("Validation failed:", error.message);
}
}
More Complex Custom Error
class APIError extends Error {
constructor(message, statusCode, response) {
super(message);
this.name = "APIError";
this.statusCode = statusCode;
this.response = response;
}
}
try {
throw new APIError("Not found", 404, { error: "Resource not found" });
} catch (error) {
if (error instanceof APIError) {
console.log(`API Error ${error.statusCode}: ${error.message}`);
}
}
Finally Block
The finally block always executes:
function example() {
try {
console.log("Try block");
return "from try";
} catch (error) {
console.log("Catch block");
return "from catch";
} finally {
console.log("Finally block"); // Always runs
}
}
console.log(example());
// Output:
// Try block
// Finally block
// from try
Cleanup with Finally
function readFile(filename) {
let file;
try {
file = openFile(filename);
return file.read();
} catch (error) {
console.log("Error reading file:", error.message);
} finally {
if (file) {
file.close(); // Always close the file
}
}
}
Practical Examples
Parsing JSON Safely
function parseJSON(jsonString) {
try {
return JSON.parse(jsonString);
} catch (error) {
console.log("Invalid JSON:", error.message);
return null;
}
}
console.log(parseJSON('{"name": "Alice"}')); // { name: "Alice" }
console.log(parseJSON("invalid")); // null
API Call with Error Handling
async function fetchUser(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.log("Failed to fetch user:", error.message);
return null;
}
}
Validation with Custom Errors
class ValidationError extends Error {
constructor(field, message) {
super(`${field}: ${message}`);
this.name = "ValidationError";
this.field = field;
}
}
function validateUser(user) {
if (!user.name) {
throw new ValidationError("name", "Name is required");
}
if (!user.email) {
throw new ValidationError("email", "Email is required");
}
if (user.age < 18) {
throw new ValidationError("age", "Must be 18 or older");
}
return true;
}
try {
validateUser({ name: "Alice", email: "[email protected]", age: 16 });
} catch (error) {
if (error instanceof ValidationError) {
console.log(`Validation error in ${error.field}: ${error.message}`);
}
}
Retry Logic
async function retryOperation(fn, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (i === maxRetries - 1) {
throw error; // Last attempt failed
}
console.log(`Attempt ${i + 1} failed, retrying...`);
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
}
// Usage
retryOperation(async () => {
const response = await fetch('/api/data');
return response.json();
});
Error Handling Best Practices
Be Specific
// Good - specific error handling
try {
const data = JSON.parse(jsonString);
} catch (error) {
if (error instanceof SyntaxError) {
console.log("Invalid JSON format");
} else {
console.log("Unexpected error:", error);
}
}
// Avoid - too broad
try {
// lots of code
} catch (error) {
console.log("Something went wrong");
}
Don’t Swallow Errors Silently
// Bad - error is ignored
try {
riskyOperation();
} catch (error) {
// Nothing happens
}
// Good - error is logged or handled
try {
riskyOperation();
} catch (error) {
console.error("Operation failed:", error);
// Take appropriate action
}
Use Finally for Cleanup
// Good - cleanup guaranteed
try {
// operation
} finally {
cleanup();
}
// Avoid - cleanup might not run
try {
// operation
}
catch (error) {
cleanup();
throw error;
}
Provide Context
// Good - context helps debugging
try {
const user = await fetchUser(userId);
} catch (error) {
throw new Error(`Failed to fetch user ${userId}: ${error.message}`);
}
// Less helpful
try {
const user = await fetchUser(userId);
} catch (error) {
throw error;
}
Related Resources
Official Documentation
External Resources
Summary
- Try-catch-finally: fundamental error handling structure
- Error types: SyntaxError, ReferenceError, TypeError, RangeError
- Throw: create errors with
throw new Error() - Custom errors: extend Error class for domain-specific errors
- Finally: guaranteed cleanup code
- Best practices: be specific, don’t swallow errors, provide context
Next Steps
- Debugging JavaScript: Tools and Techniques
- Custom Errors and Error Types
- Async/Await: Modern Asynchronous Programming
- Error Handling with Promises and Async/Await
- Testing Basics with Jest
Comments