Error Handling
Error Handling, “try…catch”
Errors happen. JavaScript provides try...catch to handle them gracefully.
Basic Syntax
try {
// Code that might throw an error
let result = JSON.parse("{invalid json}");
console.log("This won't run");
} catch (error) {
// Handle the error
console.log("Error caught:", error.message);
} finally {
// Always runs (optional)
console.log("Cleanup code");
}
How it Works
try {
console.log("Start of try");
undefinedFunction(); // This throws an error
console.log("End of try"); // Never reached
} catch (err) {
console.log("Error:", err.message); // → "Error: undefinedFunction is not defined"
}
console.log("Code continues..."); // → "Code continues..."
Only Runtime Errors
try...catch only catches runtime errors - not syntax errors:
try {
// Syntax error - not caught (script stops)
{{{{{{{{;
} catch (err) {
// Never reached
}
Error Object
The catch block receives an error object with properties:
try {
throw new Error("Something went wrong");
} catch (err) {
console.log(err.name); // → "Error"
console.log(err.message); // → "Something went wrong"
console.log(err.stack); // → Stack trace (string)
}
Throwing Our Own Errors
Use throw to generate an error intentionally:
let age = 15;
try {
if (age < 18) {
throw new Error("Access denied - too young");
}
console.log("Welcome");
} catch (err) {
console.log(err.message); // → "Access denied - too young"
}
Throw Anything
You can throw any value, but throwing Error objects is best practice:
throw "Error string"; // String
throw 42; // Number
throw true; // Boolean
throw { code: 404, msg: "Not Found" }; // Object
throw new Error("Error"); // Error object (recommended)
try…catch…finally
finally always runs, regardless of whether an error occurred:
function processData(data) {
try {
console.log("Processing...");
if (!data) throw new Error("No data");
console.log("Success:", data);
return "Done";
} catch (err) {
console.log("Error:", err.message);
return "Failed";
} finally {
console.log("Cleanup - always runs");
}
}
console.log(processData("test"));
// → "Processing..."
// → "Success: test"
// → "Cleanup - always runs"
// → "Done"
console.log(processData(null));
// → "Processing..."
// → "Error: No data"
// → "Cleanup - always runs"
// → "Failed"
Even
returnin try/catch doesn’t prevent finally from running.
Nested try…catch
try {
try {
throw new Error("Inner error");
} catch (innerErr) {
console.log("Inner catch:", innerErr.message);
throw new Error("Rethrown from inner"); // Rethrow
}
} catch (outerErr) {
console.log("Outer catch:", outerErr.message);
}
// → "Inner catch: Inner error"
// → "Outer catch: Rethrown from inner"
Rethrowing
Sometimes you want to handle only certain errors and rethrow others:
try {
let data = JSON.parse(userInput);
process(data);
} catch (err) {
if (err instanceof SyntaxError) {
console.log("Invalid JSON:", err.message);
} else {
throw err; // Unknown error - rethrow
}
}
Global Error Handler
// Browser
window.onerror = function(message, url, line, col, error) {
console.log("Global error:", message);
return true; // Prevent default handling
};
// Node.js
process.on("uncaughtException", (err) => {
console.error("Uncaught:", err);
process.exit(1);
});
Demo: Error Handling Demo
JavaScript
function divide(a, b) {
if (b === 0) {
throw new Error('Division by zero!');
}
return a / b;
}
function safeDivide(a, b) {
try {
let result = divide(a, b);
return 'Result: ' + result;
} catch (err) {
return 'Error: ' + err.message;
} finally {
document.write('(Operation attempted: ' + a + ' / ' + b + ')<br>');
}
}
document.write(safeDivide(10, 2) + '<br>');
document.write(safeDivide(10, 0) + '<br>');
document.write(safeDivide(20, 4) + '<br>');
Live Output Window
Custom Errors, Extending Error
Create custom error types for specific error scenarios:
class ValidationError extends Error {
constructor(field, message) {
super(message);
this.name = "ValidationError";
this.field = field;
}
}
class NetworkError extends Error {
constructor(statusCode, message) {
super(message);
this.name = "NetworkError";
this.statusCode = statusCode;
}
}
class AuthError extends Error {
constructor(message) {
super(message);
this.name = "AuthError";
}
}
Using Custom Errors
function validateUser(user) {
if (!user.name) {
throw new ValidationError("name", "Name is required");
}
if (user.age < 0) {
throw new ValidationError("age", "Age must be positive");
}
if (user.age < 18) {
throw new ValidationError("age", "Must be 18 or older");
}
return true;
}
function saveUser(user) {
if (!navigator.onLine) {
throw new NetworkError(503, "Service unavailable");
}
// Save to server...
}
try {
let user = { name: "", age: -1 };
validateUser(user);
saveUser(user);
} catch (err) {
if (err instanceof ValidationError) {
console.log(`Validation failed for ${err.field}: ${err.message}`);
} else if (err instanceof NetworkError) {
console.log(`Network error (${err.statusCode}): ${err.message}`);
} else {
console.log("Unknown error:", err);
}
}
// → "Validation failed for name: Name is required"
Error Hierarchy
// Base error
class AppError extends Error {
constructor(message) {
super(message);
this.name = this.constructor.name;
}
}
// Specific errors
class ValidationError extends AppError {}
class NotFoundError extends AppError {
constructor(resource, id) {
super(`${resource} with id '${id}' not found`);
this.resource = resource;
this.id = id;
}
}
class PermissionError extends AppError {
constructor(action) {
super(`Permission denied: ${action}`);
this.action = action;
}
}
// Usage with instanceof hierarchy
try {
throw new NotFoundError("User", 42);
} catch (err) {
if (err instanceof NotFoundError) {
console.log(err.message); // → "User with id '42' not found"
} else if (err instanceof AppError) {
console.log("App error:", err.message);
} else {
console.log("Unknown error");
}
}
Wrapping Errors
Sometimes you want to add context to an error:
class DatabaseError extends Error {
constructor(operation, originalError) {
super(`Database ${operation} failed: ${originalError.message}`);
this.name = "DatabaseError";
this.originalError = originalError;
}
}
try {
try {
// Database operation
throw new Error("Connection timeout");
} catch (err) {
throw new DatabaseError("insert", err);
}
} catch (err) {
console.log(err.message); // → "Database insert failed: Connection timeout"
console.log(err.originalError); // → The original error
}
Demo: Custom Errors Demo
JavaScript
class AppError extends Error {
constructor(message) {
super(message);
this.name = this.constructor.name;
}
}
class NegativeNumberError extends AppError {
constructor(value) {
super('Negative numbers not allowed: ' + value);
this.value = value;
}
}
function squareRoot(n) {
if (n < 0) {
throw new NegativeNumberError(n);
}
return Math.sqrt(n);
}
function calculate(n) {
try {
let result = squareRoot(n);
return '√' + n + ' = ' + result.toFixed(4);
} catch (err) {
if (err instanceof NegativeNumberError) {
return 'Error: ' + err.message;
}
return 'Unknown error';
}
}
document.write(calculate(16) + '<br>');
document.write(calculate(-4) + '<br>');
document.write(calculate(25) + '<br>');
document.write(calculate(-9) + '<br>');
Live Output Window