try / catch
Wrap code that might throw an error in a try block. If any line throws,
execution jumps immediately to the catch block with the error object.
// Basic try/catch
try {
const result = JSON.parse("invalid json{");
console.log(result); // never reached
} catch (error) {
console.log(error.name); // "SyntaxError"
console.log(error.message); // "Unexpected token i in JSON..."
}
// Code after catch continues normally
console.log("Program continues"); // this runs
// Accessing a property on null
try {
const user = null;
console.log(user.name); // TypeError!
} catch (err) {
console.log("Caught:", err.message);
// "Caught: Cannot read properties of null"
}
// Nested try/catch
try {
try {
throw new Error("Inner error");
} catch (inner) {
console.log("Inner catch:", inner.message);
throw inner; // re-throw to outer catch
}
} catch (outer) {
console.log("Outer catch:", outer.message);
}
The Error Object
When an error is caught, the catch parameter is an Error object with useful properties:
try {
null.property; // throws TypeError
} catch (err) {
console.log(err.name); // "TypeError"
console.log(err.message); // "Cannot read properties of null"
console.log(err.stack); // Full stack trace (for debugging)
console.log(err instanceof TypeError); // true
console.log(err instanceof Error); // true (all errors extend Error)
}
// Creating errors manually
const e = new Error("Something went wrong");
console.log(e.name); // "Error"
console.log(e.message); // "Something went wrong"
// Logging error objects
function logError(err) {
console.error(`[${err.name}] ${err.message}`);
if (process.env.NODE_ENV === "development") {
console.error(err.stack);
}
}
Built-in Error Types
| Error Type | When it occurs | Example |
|---|---|---|
Error | Generic base error | throw new Error("msg") |
TypeError | Wrong type used | null.property |
ReferenceError | Undefined variable accessed | undeclaredVar |
SyntaxError | Invalid code syntax | JSON.parse("{") |
RangeError | Value out of valid range | new Array(-1) |
URIError | Bad URI encoding | decodeURI("%") |
// Handling different error types differently
try {
riskyOperation();
} catch (err) {
if (err instanceof TypeError) {
console.log("Type problem:", err.message);
} else if (err instanceof RangeError) {
console.log("Range problem:", err.message);
} else {
throw err; // re-throw unexpected errors
}
}
Throwing Errors
Use throw to raise an error intentionally - for invalid input, business rule
violations, or unexpected states. Always throw Error objects (not strings or numbers).
// Throw an Error object (always use Error, not raw strings)
function divide(a, b) {
if (typeof a !== "number" || typeof b !== "number") {
throw new TypeError("Arguments must be numbers");
}
if (b === 0) {
throw new RangeError("Cannot divide by zero");
}
return a / b;
}
try {
console.log(divide(10, 2)); // 5
console.log(divide(10, 0)); // throws RangeError
} catch (err) {
console.log(`${err.name}: ${err.message}`);
// "RangeError: Cannot divide by zero"
}
// Throw stops execution immediately
function validate(user) {
if (!user.name) throw new Error("Name is required");
if (!user.email) throw new Error("Email is required");
if (user.age < 0) throw new RangeError("Age cannot be negative");
return true;
}
// Anti-pattern: throwing strings (loses stack trace)
// throw "something went wrong"; // avoid!
finally
The finally block always runs after try and catch,
regardless of success or failure. Use it for cleanup.
// finally always runs
function fetchData(url) {
let loading = true;
try {
const data = getData(url);
return data;
} catch (err) {
console.error("Failed:", err.message);
return null;
} finally {
loading = false; // always hides spinner
console.log("Request complete"); // always runs
}
}
// finally with return - the finally return wins!
function example() {
try {
return "try";
} finally {
return "finally"; // overrides try's return
}
}
console.log(example()); // "finally"
// Practical: close database connection
async function runQuery(sql) {
const conn = await db.connect();
try {
return await conn.query(sql);
} catch (err) {
throw err; // re-throw after logging
} finally {
await conn.close(); // always close the connection
}
}
Custom Error Classes
Extend the built-in Error class to create domain-specific errors with
additional context. This makes instanceof checks possible.
// Custom error class
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = "ValidationError";
this.field = field;
}
}
class NotFoundError extends Error {
constructor(resource, id) {
super(`${resource} with id ${id} not found`);
this.name = "NotFoundError";
this.statusCode = 404;
}
}
// Using custom errors
function getUser(id) {
const user = db.findById(id);
if (!user) throw new NotFoundError("User", id);
return user;
}
function createUser(data) {
if (!data.email) throw new ValidationError("Email is required", "email");
if (!data.name) throw new ValidationError("Name is required", "name");
return db.insert(data);
}
// Handling custom errors
try {
createUser({ name: "Alice" }); // missing email
} catch (err) {
if (err instanceof ValidationError) {
console.log(`Validation failed on field '${err.field}': ${err.message}`);
} else if (err instanceof NotFoundError) {
console.log(`Not found (${err.statusCode}): ${err.message}`);
} else {
throw err; // re-throw unexpected errors
}
}
Async Error Handling
// try/catch with async/await
async function loadUser(id) {
try {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
} catch (err) {
console.error("Failed to load user:", err.message);
return null;
}
}
// Promise .catch()
fetch("/api/data")
.then(res => res.json())
.then(data => console.log(data))
.catch(err => console.error("Error:", err.message));
// Global unhandled rejection handler (browser)
window.addEventListener("unhandledrejection", event => {
console.error("Unhandled promise rejection:", event.reason);
event.preventDefault(); // prevents browser default logging
});
// Node.js unhandled rejections
process.on("unhandledRejection", (reason, promise) => {
console.error("Unhandled Rejection at:", promise, "reason:", reason);
process.exit(1);
});