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

Search for a command to run...
How to catch, manage, and learn from the inevitable — without letting your application crash and burn.

No comments yet. Be the first to comment.
Node.js doesn't use multiple threads to handle concurrency — and that's not a weakness. Here's exactly how it pulls off handling thousands of connections without breaking a sweat.

Stop confusing params and query in Express.js

Table of Contents What is cURL — in very simple terms Why programmers need cURL Making your first request Understanding request and response Using cURL to talk to APIs Common mistakes beginners

A practical guide to where files live, how to expose them over HTTP, and the security rules you cannot skip.

Understanding authentication approaches · stateful vs stateless · when to use each

XP-VISHAL-WEB-DEV-COHORT-2026-BLOGS
24 posts
How to catch, manage, and learn from the inevitable — without letting your application crash and burn.
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.
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
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 |
tryandcatchThe 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
}
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.
finallyBlockfunction 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.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.
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.
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.");
}
}
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.
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 .stack
finally { } — 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 Error to create typed, descriptive errors; use instanceof to handle them selectively
Re-throw unknown errors — Never silently swallow errors you didn't expect; let them propagate
Async errors — Use try/catch with async/await, or .catch() on Promise chains