-
-
Notifications
You must be signed in to change notification settings - Fork 2
JavaScript Error Handling
Mattscreative edited this page Feb 21, 2026
·
1 revision
- Introduction
- Error Types
- Try/Catch/Finally
- Throwing Errors
- Custom Errors
- Async Error Handling
- Debugging Techniques
- Logging Best Practices
- Error Recovery Patterns
- Testing for Errors
Error handling is crucial for building robust applications. Proper error handling helps you identify bugs, recover from failures, and provide good user experiences.
// Error - base error object
const error = new Error("Something went wrong");
console.log(error.message); // "Something went wrong"
console.log(error.name); // "Error"
console.log(error.stack); // Stack trace
// TypeError - when value is not expected type
const obj = {};
obj(); // TypeError: obj is not a function
// ReferenceError - using undefined variable
console.log(undefinedVariable); // ReferenceError
// SyntaxError - invalid syntax (can't catch in same file)
JSON.parse("{invalid}"); // SyntaxError
// RangeError - value outside range
const arr = new Array(-1); // RangeError: Invalid array length
// URIError - invalid URI
decodeURIComponent("%"); // URIError
// EvalError - error in eval()
eval("invalid syntax"); // EvalErrortry {
someFunction();
} catch (error) {
if (error instanceof TypeError) {
console.log("Type error:", error.message);
} else if (error instanceof ReferenceError) {
console.log("Reference error:", error.message);
} else if (error instanceof RangeError) {
console.log("Range error:", error.message);
} else if (error instanceof SyntaxError) {
console.log("Syntax error:", error.message);
} else {
throw error; // Re-throw unknown errors
}
}// Basic syntax
try {
// Code that might throw an error
const result = riskyOperation();
console.log("Result:", result);
} catch (error) {
// Handle the error
console.error("Error occurred:", error.message);
}
// Without error - skip catch
try {
const x = 5;
console.log("Success:", x);
} catch (error) {
console.log("This won't run");
}
// With error
try {
throw new Error("Oops!");
} catch (error) {
console.log("Caught:", error.message); // "Caught: Oops!"
}// finally always runs
try {
console.log("In try");
throw new Error("Error!");
} catch (error) {
console.log("In catch:", error.message);
} finally {
console.log("In finally - cleanup here");
}
// finally runs even with return
function example() {
try {
return "from try";
} finally {
console.log("finally runs");
}
}
// finally runs even with throw
function example2() {
try {
throw new Error("Error!");
} finally {
console.log("finally still runs");
}
}try {
try {
throw new Error("Inner error");
} catch (innerError) {
console.log("Caught inner:", innerError.message);
throw new Error("Outer error");
}
} catch (outerError) {
console.log("Caught outer:", outerError.message);
}// Throw error
throw new Error("Something went wrong");
// Throw other types (not recommended)
throw "Error message"; // String - bad practice
throw 42; // Number - bad practice
// Conditional throwing
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(error.message); // "Cannot divide by zero"
}const error = new Error("Custom message");
// Properties available
console.log(error.name); // "Error"
console.log(error.message); // "Custom message"
console.log(error.stack); // Full stack trace
console.log(error.cause); // ES2022 - original error (if passed)// Custom error class
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
// Maintains proper stack trace
Error.captureStackTrace(this, this.constructor);
}
}
class NotFoundError extends Error {
constructor(resource) {
super(`${resource} not found`);
this.name = "NotFoundError";
Error.captureStackTrace(this, this.constructor);
}
}
// Usage
function getUser(id) {
const user = database.find(id);
if (!user) {
throw new NotFoundError("User");
}
return user;
}
try {
getUser(999);
} catch (error) {
if (error instanceof NotFoundError) {
console.log(error.message); // "User not found"
}
}// Error with additional properties
class APIError extends Error {
constructor(message, statusCode, details = {}) {
super(message);
this.name = "APIError";
this.statusCode = statusCode;
this.details = details;
Error.captureStackTrace(this, this.constructor);
}
}
// Error with cause
try {
try {
fetchData();
} catch (error) {
// Wrap with additional context
throw new Error("Failed to load user data", { cause: error });
}
} catch (error) {
console.log(error.cause); // Original error
}// Basic async error handling
async function fetchData(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Fetch failed:", error.message);
throw error;
}
}
// Using the function
async function main() {
try {
const data = await fetchData("https://api.example.com/data");
console.log("Data:", data);
} catch (error) {
console.error("Failed to fetch data:", error.message);
}
}// .catch() handler
fetch("https://api.example.com/data")
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error("Error:", error.message));
// .catch() with multiple errors
Promise.all([
fetch("/api/users").then(r => r.json()),
fetch("/api/posts").then(r => r.json())
])
.then(([users, posts]) => console.log(users, posts))
.catch(error => console.error("At least one request failed:", error));// Promise.all - fails fast if any promise rejects
async function fetchAll() {
try {
const results = await Promise.all([
fetch("/api/data1").then(r => r.json()),
fetch("/api/data2").then(r => r.json())
]);
return results;
} catch (error) {
console.error("One or more requests failed");
throw error;
}
}
// Promise.allSettled - never rejects
async function fetchAllSafe() {
const results = await Promise.allSettled([
fetch("/api/data1").then(r => r.json()),
fetch("/api/data2").then(r => r.json())
]);
const successful = results
.filter(r => r.status === "fulfilled")
.map(r => r.value);
const failed = results
.filter(r => r.status === "rejected")
.map(r => r.reason);
return { successful, failed };
}// Different console methods
console.log("Info message");
console.info("Information");
console.warn("Warning");
console.error("Error");
// Console with formatting
console.log("User: %s, Age: %d", "Alice", 25);
console.log("Object: %o", { a: 1, b: 2 });
// Grouping console output
console.group("User Details");
console.log("Name: Alice");
console.log("Age: 25");
console.groupEnd();
// Table output (for arrays/objects)
const users = [
{ name: "Alice", age: 25 },
{ name: "Bob", age: 30 }
];
console.table(users);
// Timing
console.time("fetch");
await fetchData();
console.timeEnd("fetch");
// Stack trace
console.trace("Stack trace");// Debugger keyword - pauses execution
function processData(data) {
debugger; // Execution pauses here in DevTools
// inspect variables
return data.map(item => item * 2);
}
// Conditional breakpoints (set in DevTools)
// Right-click line number > Add conditional breakpoint
function complexFunction(x, y) {
const result = x * y;
return result;
}// Inspect objects
const obj = { a: 1, b: 2, c: 3 };
console.log(obj);
console.dir(obj); // Interactive tree view
// Clear console
console.clear();
// Assert (only logs if false)
const x = 5;
console.assert(x > 10, "x should be greater than 10");// Log levels
const LOG_LEVELS = {
ERROR: 0,
WARN: 1,
INFO: 2,
DEBUG: 3
};
function log(level, message, meta = {}) {
if (level <= LOG_LEVELS.DEBUG) { // Set current level
console[level === LOG_LEVELS.ERROR ? "error" :
level === LOG_LEVELS.WARN ? "warn" :
"log"]({
timestamp: new Date().toISOString(),
level: Object.keys(LOG_LEVELS).find(k => LOG_LEVELS[k] === level),
message,
...meta
});
}
}
log(LOG_LEVELS.INFO, "User logged in", { userId: 123 });
log(LOG_LEVELS.ERROR, "Payment failed", { error: "Insufficient funds", amount: 100 });// Capture and log errors
window.addEventListener("error", (event) => {
console.error("Global error:", event.error);
});
// Log async errors
window.addEventListener("unhandledrejection", (event) => {
console.error("Unhandled promise rejection:", event.reason);
});
// Send to error tracking service
function logError(error, context = {}) {
const errorData = {
message: error.message,
stack: error.stack,
url: window.location.href,
timestamp: new Date().toISOString(),
userAgent: navigator.userAgent,
...context
};
// Send to server or error tracking service
console.log("Error logged:", errorData);
}// Provide fallback when feature fails
async function getUserData() {
try {
const user = await fetchUser();
const preferences = await fetchPreferences();
return { user, preferences };
} catch (error) {
console.warn("Failed to load full data:", error.message);
// Return partial data
try {
const user = await fetchUser();
return { user, preferences: {} };
} catch (userError) {
return { user: null, preferences: {} };
}
}
}
// Default values pattern
function processInput(input) {
const config = {
timeout: 5000,
retries: 3,
...input
};
return config;
}// Retry with exponential backoff
async function retry(fn, maxAttempts = 3, delay = 1000) {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await fn();
} catch (error) {
if (attempt === maxAttempts) {
throw error;
}
const waitTime = delay * Math.pow(2, attempt - 1);
console.warn(`Attempt ${attempt} failed, retrying in ${waitTime}ms`);
await new Promise(r => setTimeout(r, waitTime));
}
}
}
// Usage
const data = await retry(() => fetchData());class CircuitBreaker {
constructor(failureThreshold = 5, timeout = 60000) {
this.failureThreshold = failureThreshold;
this.timeout = timeout;
this.failures = 0;
this.lastFailureTime = null;
this.state = "CLOSED"; // CLOSED, OPEN, HALF_OPEN
}
async execute(fn) {
if (this.state === "OPEN") {
if (Date.now() - this.lastFailureTime > this.timeout) {
this.state = "HALF_OPEN";
} else {
throw new Error("Circuit breaker is OPEN");
}
}
try {
const result = await fn();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
onSuccess() {
this.failures = 0;
this.state = "CLOSED";
}
onFailure() {
this.failures++;
this.lastFailureTime = Date.now();
if (this.failures >= this.failureThreshold) {
this.state = "OPEN";
}
}
}// Test that function throws
function divide(a, b) {
if (b === 0) throw new Error("Cannot divide by zero");
return a / b;
}
// Using expect().toThrow()
test("divide throws on zero", () => {
expect(() => divide(10, 0)).toThrow();
expect(() => divide(10, 0)).toThrow("Cannot divide by zero");
});
// Test async errors
test("fetchData throws on error", async () => {
await expect(fetchData("invalid-url")).rejects.toThrow();
});
// Test custom errors
test("throws ValidationError for invalid input", () => {
expect(() => validateInput("")).toThrow(ValidationError);
});// Try/catch
try {
riskyOperation();
} catch (error) {
console.error(error.message);
} finally {
cleanup();
}
// Throw error
throw new Error("Message");
// Custom error
class MyError extends Error {
constructor(message) {
super(message);
this.name = "MyError";
}
}
// Async error handling
async function main() {
try {
const data = await fetchData();
} catch (error) {
console.error(error);
}
}
// Promise error handling
fetchData()
.catch(error => console.error(error));