Lesson 10 • Intermediate
Error Handling
Errors are not signs of failure — they are signals. Learn everything from basic try/catch to advanced real-world error architectures used in production apps.
What You'll Learn in This Lesson
- ✓Catch errors with try/catch/finally
- ✓Throw your own custom errors
- ✓Identify built-in error types (TypeError, ReferenceError)
- ✓Handle errors in async code
- ✓Create custom error classes
- ✓Build robust production-level error systems
💡 Running Code Locally: While this online editor runs real JavaScript, some advanced examples may have limitations. For the best experience:
- Download Node.js to run JavaScript on your computer
- Use your browser's Developer Console (Press F12) to test code snippets
- Create a
.htmlfile with<script>tags and open it in your browser
Why Error Handling Matters
When errors are ignored:
- Your app crashes suddenly
- Users lose progress
- Data becomes corrupted
- Security becomes weaker
- Debugging becomes 10× harder
JavaScript gives you extremely powerful tools to handle, detect, filter, categorize, and recover from errors — synchronously AND asynchronously.
1. What Is an Error in JavaScript?
An error occurs when JavaScript cannot complete an operation. Examples:
- Trying to use an undefined variable
- Calling a function that doesn't exist
- Invalid API responses
- Network failures
- Logic errors in your code
- Unexpected data formats
By default, errors stop execution. Your job is to catch, interpret, and recover from them.
2. Try...Catch — The Foundation
try {
// Code that might fail
riskyOperation();
} catch (error) {
console.error("Error occurred:", error.message);
} finally {
console.log("Cleanup runs no matter what.");
}What happens:
tryruns your potentially-dangerous codecatchreceives the error if thrownfinallyALWAYS runs (cleanup, closing files, removing loaders, etc.)
3. Throwing Your Own Errors
JavaScript allows you to create and throw your own errors:
function divide(a, b) {
if (b === 0) {
throw new Error("Cannot divide by zero");
}
return a / b;
}
try {
divide(10, 0);
} catch (error) {
console.log("Caught:", error.message);
}Why throw custom errors?
- You can prevent further execution
- You can communicate clearly what went wrong
- You can enforce data validation
- You create predictable behaviour
4. Built-In Error Types
Error
General-purpose error
TypeError
Incorrect data type. Example:
null.toUpperCase();
ReferenceError
Variable not defined:
console.log(x); // x is not declared
SyntaxError
Incorrect syntax:
let a = ; // invalid
RangeError
Value outside allowed range:
new Array(-2);
URIError
Triggered by malformed URI sequences
5. Using Error Names to Handle Smartly
try {
risky();
} catch (error) {
if (error instanceof TypeError) {
console.log("Type problem!");
} else if (error instanceof ReferenceError) {
console.log("You referenced something wrong!");
} else {
console.log("Unknown error:", error.message);
}
}This lets you create fine-grained error logic.
6. Error Handling in Asynchronous Code
With async/await:
async function getUser() {
try {
const res = await fetch(url);
if (!res.ok) throw new Error("HTTP Error " + res.status);
return await res.json();
} catch (err) {
console.error("Fetch failed:", err.message);
return { error: true, msg: err.message };
}
}Why async error handling matters:
- Real-world JavaScript is 70% async
- APIs fail
- Network issues happen
- Users have slow internet
- Data can be malformed
7. Re-Throwing Errors
Sometimes you want to catch an error, log it, then pass it upward:
async function loadData() {
try {
return await getData();
} catch (err) {
console.error("Handled locally:", err.message);
throw err; // re-throw
}
}Useful when:
- Logging errors
- Adding context
- Creating layered architecture
8. Creating Custom Error Classes
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
}
}
function validateAge(age) {
if (age < 0) throw new ValidationError("Age cannot be negative");
}Benefits of custom errors:
- Cleaner error organization
- More predictable logic
- Easier debugging
- Perfect for large apps
9. Nested Try...Catch Blocks
try {
try {
throw new Error("Inner failure");
} catch (inner) {
console.log("Inner caught:", inner.message);
throw new Error("Outer failure");
}
} catch (outer) {
console.log("Outer caught:", outer.message);
}Nested errors often appear when:
- Parsing inside processing
- Handling multiple API calls
- Running multi-stage operations
10. Best Practices for Error Handling
11. Real-World Example — Payment Processing
function chargeCard(card, amount) {
if (!card.number) {
throw new ValidationError("Card number missing");
}
if (amount <= 0) {
throw new ValidationError("Amount must be greater than 0");
}
// Simulate charge...
return { success: true };
}
// With layered handling:
try {
chargeCard(card, 10);
} catch (err) {
if (err instanceof ValidationError) {
console.log("User made a mistake:", err.message);
} else {
console.log("Internal failure:", err.message);
}
}This mimics real e-commerce systems.
12. Real-World Example — Robust API Fetching
async function fetchProduct(id) {
try {
const res = await fetch(`/product/${id}`);
if (!res.ok) {
if (res.status === 404) throw new Error("Product not found");
if (res.status === 500) throw new Error("Server error");
}
const data = await res.json();
return data;
} catch (err) {
console.error("API error:", err.message);
return { error: true };
}
}Now your UI doesn't crash when the API fails — it gracefully recovers.
13. Real-World Example — Form Validation
function validateForm(user) {
if (!user.email.includes("@")) {
throw new ValidationError("Invalid email format");
}
if (user.password.length < 8) {
throw new ValidationError("Password too short");
}
return true;
}
// Frontend:
try {
validateForm(user);
console.log("Form valid");
} catch (err) {
showErrorToUser(err.message);
}14. Advanced Async Error Handling
Asynchronous code is where 90% of real production errors happen.
Why?
- APIs timeout
- Backend services go down
- Internet drops
- JSON parsing fails
- Unexpected response shape
- Race conditions
- Rate limits
- Authentication tokens expire
You MUST master async error strategies to build real-world websites, apps, and SaaS systems.
15. Fetch Errors Aren't Always "Errors"
fetch() only throws errors for:
- Network down
- Domain unreachable
- DNS failure
- CORS failures
- Request blocked
⚠️ It does NOT throw errors for bad HTTP codes (404, 500, 403, 429)
You must check:
if (!res.ok) {
throw new Error("HTTP " + res.status);
}Without this check, your code will behave like everything is fine when it absolutely isn't.
16. Retry Logic with Exponential Backoff
Real-world apps must recover from errors gracefully:
async function retryFetch(url, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const res = await fetch(url);
if (res.ok) return res.json();
} catch (err) {
if (i === retries - 1) throw err;
}
await new Promise(r => setTimeout(r, 200 * (i + 1)));
}
}Retries at: 200ms → 400ms → 600ms. This is used by Google, AWS, Netflix, Stripe, PayPal.
17. Defensive Programming
Defensive programming = writing code that assumes things will go wrong.
Defensive JSON Parsing:
function safeJSON(response) {
try {
return response.json();
} catch {
return { error: "Invalid JSON" };
}
}Defensive Type Checking:
function safeMultiply(a, b) {
if (typeof a !== "number" || typeof b !== "number") {
throw new TypeError("Arguments must be numbers");
}
return a * b;
}Defensive Optional Chaining:
const city = user?.address?.location?.city ?? "Unknown";
No more crashes.
18. Global Error Handling
Most frameworks implement a global error handler:
Browser Global Error Handler:
window.onerror = function(message, source, line, column, error) {
console.log("Global error:", message);
};Global Promise Rejections:
window.onunhandledrejection = function(event) {
console.log("Unhandled rejection:", event.reason);
};This prevents silent failures.
19. Production-Grade Error Handler
A complete production-grade Fetch handler:
async function api(url, options = {}) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 8000);
try {
const res = await fetch(url, {
...options,
signal: controller.signal
});
if (!res.ok) {
const text = await res.text();
throw new Error(`HTTP ${res.status}: ${text}`);
}
return res.json();
} catch (e) {
if (e.name === "AbortError") {
throw new Error("Request timed out");
}
throw e;
} finally {
clearTimeout(timeout);
}
}This handles:
- Timeout
- Cancellation
- Status errors
- Network errors
- Clean-up
- JSON parsing
Comprehensive Error Handling Examples
Practice all error handling concepts including try/catch, custom errors, async handling, and defensive programming
// Comprehensive Error Handling Examples
console.log("=== Error Handling Mastery ===\n");
// 1. Basic try...catch
console.log("1. Basic Try...Catch:");
try {
console.log("Trying risky operation...");
throw new Error("Something went wrong!");
} catch (error) {
console.error("Caught error:", error.message);
} finally {
console.log("Cleanup complete\n");
}
// 2. Custom error class
console.log("2. Custom Error Class:");
class ValidationError extends Error {
constructor(message)
...20. Common JavaScript Error Scenarios
- ✔ JSON parsing failure
- ✔ Network offline
- ✔ Missing properties
- ✔ Undefined variable
- ✔ Null access
- ✔ Wrong type
- ✔ Not catching async error
- ✔ Silent Promise rejection
- ✔ Invalid function argument
- ✔ Race condition
You will encounter these daily in real development.
🎯 Practice Projects for Mastery
Project 1 — "Safe Calculator"
- Validate numbers
- Throw errors for wrong input
- Catch and display friendly messages
- Try/catch around each operation
Project 2 — "Robust Fetch Wrapper"
Build a function: safeFetch(url, retries = 3)
- Retry logic
- Throw on bad HTTP codes
- Distinguish network vs HTTP error
- Return JSON or fallback
Project 3 — "Form Validator"
- Use multiple custom error classes
- Validate email, password, username
- Show UI messages
Project 4 — "Global Error Handler Demo"
- Demonstrate window.onerror
- Demonstrate window.onunhandledrejection
📋 Quick Reference — Error Handling
| Method | Purpose |
|---|---|
| try...catch | Catch errors in a block of code |
| throw | Manually trigger an error |
| finally | Run code regardless of outcome |
| Error Object | Has .message and .stack |
| Custom Error | class MyError extends Error |
Lesson 10 Complete — Error Handling!
You've mastered the art of writing robust code that handles failure gracefully. This separates amateur code from professional software.
Up next: Closures & Scope — diving deep into advanced JavaScript concepts! 🧠
Sign up for free to track which lessons you've completed and get learning reminders.