Lesson 11: Error Handling - try, catch, throw
What You'll Learn
- What errors are and why they occur
- How to handle errors with try/catch
- How to throw your own errors
- Creating custom error classes
- Error handling best practices
- Finally block
Why This Matters
Errors happen in every program - a file doesn't exist, network fails, user enters invalid data. Good programs handle errors gracefully instead of crashing. Error handling makes your code robust and user-friendly.
---
Part 1: Understanding Errors
Create a new file:lesson-11/error-handling.js
What Happens When an Error Occurs?
console.log("Before error");
const result = 10 / 0; // This creates Infinity, not an error
// But this will cause an error:
const user = null;
console.log(user.name); // ❌ Error: Cannot read property 'name' of null
console.log("After error"); // This never runs!
When an error occurs:
- Program stops executing
- Error message is shown
- Code after the error never runs
---
Part 2: The try/catch Statement
try/catch lets you handle errors without crashing:
Basic Syntax
try {
// Code that might cause an error
} catch (error) {
// Code to run if error occurs
}
Simple Example
try {
console.log("Trying something risky...");
const user = null;
console.log(user.name); // This will error
console.log("This line never runs");
} catch (error) {
console.log("An error occurred!");
console.log(error);
}
console.log("Program continues!"); // This DOES run!
Output:
Trying something risky...
An error occurred!
TypeError: Cannot read property 'name' of null
Program continues!
---
Part 3: Working with Error Objects
The catch block receives an error object:
try {
const numbers = [1, 2, 3];
console.log(numbers[10].toFixed(2)); // undefined.toFixed() causes error
} catch (error) {
if (error instanceof Error) {
console.log("Error name:", error.name);
console.log("Error message:", error.message);
console.log("Stack trace:", error.stack);
}
}
Common Error Properties
- name: Type of error (TypeError, ReferenceError, etc.)
- message: Description of what went wrong
- stack: Call stack (where error occurred)
---
Part 4: Throwing Your Own Errors
You can create errors with the throw keyword:
Basic throw
function divide(a, b) {
if (b === 0) {
throw new Error("Cannot divide by zero!");
}
return a / b;
}
try {
console.log(divide(10, 2)); // 5
console.log(divide(10, 0)); // Throws error
console.log("This never runs");
} catch (error) {
if (error instanceof Error) {
console.log("Error:", error.message);
}
}
Output:
5
Error: Cannot divide by zero!
When to Throw Errors
function getUser(id) {
// Simulate database lookup
const users = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" }
];
const user = users.find(u => u.id === id);
if (!user) {
throw new Error(`User with id ${id} not found`);
}
return user;
}
try {
const user = getUser(1);
console.log(user.name); // Alice
const user2 = getUser(999); // Throws error
console.log(user2.name);
} catch (error) {
if (error instanceof Error) {
console.log("Error:", error.message);
}
}
---
Part 5: Different Error Types
JavaScript has several built-in error types:
// TypeError - Wrong type
try {
const num = "hello";
num.toFixed(2); // toFixed is for numbers, not strings
} catch (error) {
console.log(error.name); // TypeError
}
// ReferenceError - Variable doesn't exist
try {
console.log(nonExistentVariable);
} catch (error) {
console.log(error.name); // ReferenceError
}
// RangeError - Value out of range
try {
const arr = new Array(-1); // Negative length
} catch (error) {
console.log(error.name); // RangeError
}
// SyntaxError - Invalid syntax (usually caught before running)
try {
eval("const x = ;"); // Invalid syntax
} catch (error) {
console.log(error.name); // SyntaxError
}
Throwing Specific Error Types
function setAge(age) {
if (age < 0 || age > 150) {
throw new RangeError("Age must be between 0 and 150");
}
console.log(`Age set to ${age}`);
}
try {
setAge(25); // Age set to 25
setAge(200); // Throws RangeError
} catch (error) {
if (error instanceof RangeError) {
console.log("Range error:", error.message);
}
}
---
Part 6: Custom Error Classes
Create your own error types for better organization:
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
}
}
class NotFoundError extends Error {
constructor(message) {
super(message);
this.name = "NotFoundError";
}
}
function validateEmail(email) {
if (!email.includes("@")) {
throw new ValidationError("Invalid email format");
}
}
function findUser(id) {
// Simulate database
const users = [{ id: 1, name: "Alice" }];
const user = users.find(u => u.id === id);
if (!user) {
throw new NotFoundError(`User ${id} not found`);
}
return user;
}
try {
validateEmail("invalid-email");
} catch (error) {
if (error instanceof ValidationError) {
console.log("Validation failed:", error.message);
} else if (error instanceof NotFoundError) {
console.log("Not found:", error.message);
} else {
console.log("Unknown error:", error);
}
}
Custom Error with Additional Data
class APIError extends Error {
statusCode;
endpoint;
constructor(message, statusCode, endpoint) {
super(message);
this.name = "APIError";
this.statusCode = statusCode;
this.endpoint = endpoint;
}
}
function fetchData(endpoint) {
// Simulate API failure
const success = false;
if (!success) {
throw new APIError(
"Failed to fetch data",
404,
endpoint
);
}
}
try {
fetchData("/api/users");
} catch (error) {
if (error instanceof APIError) {
console.log(`API Error at ${error.endpoint}`);
console.log(`Status: ${error.statusCode}`);
console.log(`Message: ${error.message}`);
}
}
---
Part 7: The finally Block
finally runs whether an error occurs or not:
function processFile(filename) {
console.log("Opening file...");
try {
console.log("Processing file...");
if (filename === "error.txt") {
throw new Error("File is corrupted");
}
console.log("File processed successfully");
} catch (error) {
console.log("Error processing file:", error.message);
} finally {
// This ALWAYS runs
console.log("Closing file...");
}
console.log("Done!");
}
processFile("data.txt");
console.log("---");
processFile("error.txt");
Output:
Opening file...
Processing file...
File processed successfully
Closing file...
Done!
---
Opening file...
Processing file...
Error processing file: File is corrupted
Closing file...
Done!
When to Use finally
// Common use case: Cleanup resources
function connectToDatabase() {
const connection = { connected: true };
try {
console.log("Connecting to database...");
// Do database operations
throw new Error("Connection failed");
} catch (error) {
console.log("Database error:", error.message);
} finally {
// Always cleanup, even if error occurred
if (connection.connected) {
console.log("Closing database connection");
connection.connected = false;
}
}
}
connectToDatabase();
---
Part 8: Error Handling Best Practices
1. Be Specific with Error Messages
// ❌ Bad - Vague message
throw new Error("Invalid input");
// ✅ Good - Specific message
throw new Error("Email must contain @ symbol");
2. Handle Errors at the Right Level
// Handle errors where you can recover
function saveUser(user) {
try {
validateUser(user);
// Save to database
} catch (error) {
// Handle validation error here
console.log("Cannot save user:", error.message);
}
}
// Let errors bubble up when you can't handle them
function validateUser(user) {
if (!user.email) {
throw new Error("Email is required"); // Let caller handle this
}
}
3. Don't Catch and Ignore
// ❌ Bad - Swallowing errors
try {
riskyOperation();
} catch (error) {
// Empty catch - error is lost!
}
// ✅ Good - At least log it
try {
riskyOperation();
} catch (error) {
console.error("Error in riskyOperation:", error);
// Maybe rethrow or handle
}
4. Validate Input Early
function calculateDiscount(price, percent) {
// Validate first
if (price < 0) {
throw new Error("Price cannot be negative");
}
if (percent < 0 || percent > 100) {
throw new Error("Percent must be between 0 and 100");
}
// Now calculate safely
return price * (percent / 100);
}
---
Part 9: Practical Examples
Example 1: Safe JSON Parsing
function parseJSON(jsonString) {
try {
return JSON.parse(jsonString);
} catch (error) {
console.error("Invalid JSON:", error.message);
return null;
}
}
const validJSON = '{"name": "Alice", "age": 30}';
const invalidJSON = '{name: Alice}'; // Invalid JSON
const data1 = parseJSON(validJSON);
console.log(data1); // { name: 'Alice', age: 30 }
const data2 = parseJSON(invalidJSON);
console.log(data2); // null
Example 2: Form Validation
class ValidationError extends Error {
field;
constructor(field, message) {
super(message);
this.name = "ValidationError";
this.field = field;
}
}
function validateForm(data: FormData) {
if (data.username.length < 3) {
throw new ValidationError(
"username",
"Username must be at least 3 characters"
);
}
if (!data.email.includes("@")) {
throw new ValidationError(
"email",
"Email must contain @ symbol"
);
}
if (data.password.length < 8) {
throw new ValidationError(
"password",
"Password must be at least 8 characters"
);
}
}
function submitForm(data: FormData) {
try {
validateForm(data);
console.log("Form submitted successfully!");
} catch (error) {
if (error instanceof ValidationError) {
console.log(`Error in ${error.field}: ${error.message}`);
} else {
console.log("Unknown error:", error);
}
}
}
submitForm({
username: "Al",
email: "alice@example.com",
password: "password123"
});
// Output: Error in username: Username must be at least 3 characters
Example 3: Retry Logic
async function fetchWithRetry(
url,
maxRetries = 3
) {
let lastError: Error | null = null;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
console.log(`Attempt ${attempt} of ${maxRetries}`);
// Simulate fetch (replace with actual fetch)
if (Math.random() < 0.7) {
throw new Error("Network error");
}
return { data: "Success!" };
} catch (error) {
lastError = error as Error;
console.log(`Attempt ${attempt} failed:`, error.message);
if (attempt < maxRetries) {
console.log("Retrying...");
// In real code, add delay here
}
}
}
throw new Error(`Failed after ${maxRetries} attempts: ${lastError?.message}`);
}
// Use it
fetchWithRetry("/api/data")
.then(result => console.log("Success:", result))
.catch(error => console.log("Final error:", error.message));
---
Practice Exercises
Exercise 1: Safe Calculator
Create a calculator with error handling for division by zero, invalid inputs, etc.
Exercise 2: User Registration
Implement user registration with validation and custom error types.
Exercise 3: File Reader (Simulated)
Create a function that simulates file reading with error handling for missing files.
Exercise 4: API Call Handler
Create a function that handles API calls with retry logic and different error types.
Exercise 5: Password Validator
Create a comprehensive password validator with specific error messages.
---
Common Mistakes
Mistake 1: Catching Too Broadly
// ❌ Catches everything, even unexpected errors
try {
manyOperations();
} catch (error) {
console.log("Something failed");
}
// ✅ Be specific
try {
riskyOperation();
} catch (error) {
if (error instanceof ValidationError) {
// Handle validation
} else {
// Rethrow unexpected errors
throw error;
}
}
Mistake 2: Forgetting Async Errors
// ❌ Doesn't catch promise rejections
try {
fetch("/api/data"); // Returns promise
} catch (error) {
// Never catches!
}
// ✅ Use async/await with try/catch
async function getData() {
try {
const response = await fetch("/api/data");
} catch (error) {
// This catches promise rejections
}
}
---
Key Concepts Summary
| Concept | Purpose | Example |
|---|---|---|
| try | Code that might error | try { risky(); } |
| catch | Handle the error | catch (error) { } |
| throw | Create an error | throw new Error("msg") |
| finally | Always runs | finally { cleanup(); } |
| Custom Error | Specific error types | class MyError extends Error |
What You Learned
- ✅ How errors stop program execution
- ✅ How to catch errors with try/catch
- ✅ How to throw your own errors
- ✅ Different types of errors
- ✅ How to create custom error classes
- ✅ When to use the finally block
- ✅ Error handling best practices
- ✅ Real-world error handling patterns
What's Next?
In the next lesson, you'll learn about asynchronous programming - how to handle operations that take time, like fetching data from an API or reading files!