Error Handling in JavaScript: Try, Catch, Finally
How to catch, manage, and learn from the inevitable — without letting your application crash and burn.

How to catch, manage, and learn from the inevitable — without letting your application crash and burn.
1. What Are Errors in JavaScript?
Every JavaScript program, no matter how well written, will eventually encounter an error. An error is an unexpected condition that disrupts the normal flow of execution. JavaScript distinguishes between two fundamental kinds of problems you'll face.
Syntax Errors
These occur when the JavaScript engine cannot parse your code at all — they're caught before a single line runs. A missing bracket, a misspelled keyword, or an unexpected token will throw a SyntaxError.
syntax error example
// Missing closing parenthesis — caught immediately by the engine
console.log("Hello World" // ← SyntaxError: Unexpected end of input
Runtime Errors
These are the tricky ones. The code looks fine, so it parses successfully — but something goes wrong during execution. Accessing a property on undefined, calling something that isn't a function, or dividing by zero — these all cause runtime errors.
/ ReferenceError — variable doesn't exist
console.log(username); // ReferenceError: username is not defined
// TypeError — wrong type for an operation
const user = null;
console.log(user.name); // TypeError: Cannot read properties of null
// RangeError — value out of acceptable range
const arr = new Array(-1); // RangeError: Invalid array length
| Error Type | When It Occurs |
|---|---|
SyntaxError |
Malformed code that can't be parsed |
ReferenceError |
Accessing an undeclared variable |
TypeError |
Wrong type used in an operation |
RangeError |
Value outside allowed range |
URIError |
Malformed URI functions |
EvalError |
Issues with the eval() function |
02. Usingtryandcatch
The try...catch statement is JavaScript's primary mechanism for handling runtime errors gracefully. You place potentially dangerous code inside a try block. If an error is thrown, execution immediately jumps to the catch block — and your program keeps running instead of crashing.
try {
// Code that might throw an error
const data = JSON.parse(invalidJson);
} catch (error) {
// Runs only if an error was thrown above
console.error("Something went wrong:", error.message);
}
The catch block receives the error object as its parameter. This object carries useful properties you can inspect:
reading the error object
try {
const user = null;
console.log(user.name);
} catch (error) {
console.log(error.name); // "TypeError"
console.log(error.message); // "Cannot read properties of null"
console.log(error.stack); // Full stack trace string
}
A Real-World Example
Fetching data from an API is a classic scenario where errors can occur — the network might be down, the response might be malformed, or the server might return unexpected data.
async function getUserData(id) { try { const response = await fetch(/api/users/${id});
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
const user = await response.json();
return user;
} catch (error) {
console.error("Failed to fetch user:", error.message);
return null; // Graceful fallback
}
}
⚠ Note
try...catch only handles synchronous errors and awaited async errors. A plain Promise rejection without await will slip past an unguarded catch block.
03. ThefinallyBlock
function readFile(path) {
let fileHandle = null;
try {
fileHandle = openFile(path); // risky operation
return fileHandle.read();
} catch (error) {
console.error("Read failed:", error.message);
return null;
} finally {
// Runs even if try returned or catch ran
if (fileHandle) fileHandle.close();
console.log("File handle released.");
}
}
finally: closing database connections, stopping loading spinners, releasing locks, or logging completion — anything that must happen regardless of outcome.04. Throwing Custom Errors
JavaScript lets you throw any value, but the cleanest approach is to extend the built-in Error class. Custom error types let you be precise about what kind of thing went wrong — a failed validation is not the same as a missing resource.
"Be specific about failure. A well-named error is worth more than a thousand console logs."
custom error classes
// Define custom error types
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = "ValidationError";
this.field = field;
}
}
class NotFoundError extends Error {
constructor(resource) {
super(`${resource} was not found`);
this.name = "NotFoundError";
this.statusCode = 404;
}
}
// Use them with throw
function validateAge(age) {
if (typeof age !== "number") {
throw new ValidationError("Age must be a number", "age");
}
if (age < 0 || age > 150) {
throw new ValidationError("Age is out of range", "age");
}
return true;
}
// Catch and handle by type
try {
validateAge(-5);
} catch (error) {
if (error instanceof ValidationError) {
console.error(`Field "\({error.field}" failed: \){error.message}`);
} else {
throw error; // Re-throw unknown errors
}
}
Notice the pattern of re-throwing unknown errors. Your catch block should handle what it understands and let everything else propagate. Silently swallowing all errors is one of the most common — and most dangerous — mistakes in error handling.
05. Why Error Handling Matters
Graceful Failure vs. Crashing
Without error handling, a single thrown error terminates your entire script. A user clicking a button gets a frozen page and no feedback. With proper error handling, you can show a helpful message, retry the operation, fall back to cached data, or log the issue and keep the rest of the UI working.
Debugging Benefits
Structured error handling makes bugs easier to find. When you attach meaningful messages, capture the stack property, and log errors to a monitoring service, you receive rich context — not just "something broke." You know where it broke, what the inputs were, and how execution reached that point.
function processOrder(order) {
try {
validateOrder(order);
chargeCard(order.payment);
fulfillOrder(order);
} catch (error) {
// Log structured context — far more useful than console.log("error")
logger.error({
message: error.message,
type: error.name,
orderId: order?.id,
stack: error.stack,
timestamp: new Date().toISOString()
});
// Show user-friendly feedback
showToast("Order failed. Please try again.");
}
}
Security Considerations
What you don't show matters as much as what you do. Never expose raw error messages or stack traces to end users — they can reveal your application's internal structure. Log full details server-side; show safe, generic messages client-side.
✅ Best Practices
Always handle errors as close to their source as possible. Use custom error classes for domain-specific failures. Never silently swallow errors with empty
catchblocks. Usefinallyfor cleanup. Log with context, not just a message string.
Quick Reference
try { } — Wrap code that might throw a runtime error
catch(error) { } — Runs only when an error is thrown; receives the error object with
.name,.message, and.stackfinally { } — Always runs after try/catch, perfect for cleanup code
throw new Error(msg) — Manually throw an error from any point in your code
Custom errors — Extend
Errorto create typed, descriptive errors; useinstanceofto handle them selectivelyRe-throw unknown errors — Never silently swallow errors you didn't expect; let them propagate
Async errors — Use
try/catchwithasync/await, or.catch()on Promise chains


