Error Handling and Exceptions
About 860 wordsAbout 11 min
2025-08-05
This section covers JavaScript's error handling mechanisms, from basic try/catch blocks to advanced error patterns and custom error types.
Exception Handling Fundamentals
JavaScript uses a try-catch-finally mechanism for handling runtime errors:
try {
// Code that might throw an error
riskyOperation();
} catch (error) {
// Handle the error
console.error('Error occurred:', error.message);
} finally {
// Always executes, regardless of error
cleanup();
}
Error Object Structure: Every error in JavaScript is an object with these key properties:
message
: Human-readable error descriptionname
: Error type name (e.g., "TypeError", "ReferenceError")stack
: Stack trace showing the call hierarchycause
: (ES2022+) The underlying error that caused this error
Built-in Error Types
JavaScript provides several built-in error types:
`Error`
The base error type. All other errors inherit from it.
`TypeError`
Thrown when a value is not of the expected type.
const x = null;
x.method(); // TypeError: Cannot read property 'method' of null
`ReferenceError`
Thrown when trying to access a variable that doesn't exist.
console.log(undefinedVariable); // ReferenceError: undefinedVariable is not defined
`SyntaxError`
Thrown when there's a syntax error in the code (caught during parsing).
`RangeError`
Thrown when a value is outside its allowed range.
const arr = new Array(-1); // RangeError: Invalid array length
`URIError`
Thrown when URI handling functions are used incorrectly.
`EvalError`
Thrown when the eval()
function is used incorrectly (rarely used).
Custom Error Types
Creating custom error types allows for more specific error handling:
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = 'ValidationError';
this.field = field;
this.statusCode = 400;
}
}
class NetworkError extends Error {
constructor(message, statusCode) {
super(message);
this.name = 'NetworkError';
this.statusCode = statusCode;
}
}
// Using custom errors
function validateUser(user) {
if (!user.email) {
throw new ValidationError('Email is required', 'email');
}
if (!user.email.includes('@')) {
throw new ValidationError('Invalid email format', 'email');
}
}
try {
validateUser({ email: 'invalid' });
} catch (error) {
if (error instanceof ValidationError) {
console.log(`Validation failed on field ${error.field}: ${error.message}`);
// Handle validation error
} else {
// Handle other error types
console.error('Unexpected error:', error);
}
}
Error Propagation and Asynchronous Code
Synchronous Error Propagation:
function level3() {
throw new Error('Error from level 3');
}
function level2() {
level3(); // Error propagates up the call stack
}
function level1() {
try {
level2();
} catch (error) {
console.log('Caught in level 1:', error.message);
}
}
Asynchronous Error Handling:
// Callback pattern
function fetchData(callback) {
setTimeout(() => {
callback(new Error('Network error'), null);
}, 1000);
}
fetchData((error, data) => {
if (error) {
console.error('Callback error:', error);
return;
}
console.log('Data:', data);
});
// Promise pattern
function fetchDataPromise() {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('Network error'));
}, 1000);
});
}
fetchDataPromise()
.catch(error => console.error('Promise error:', error));
// async/await pattern
async function getData() {
try {
const data = await fetchDataPromise();
return data;
} catch (error) {
console.error('Async/await error:', error);
throw error; // Re-throw if you want caller to handle
}
}
Advanced Error Handling Patterns
Error Aggregation:
class AggregateError extends Error {
constructor(errors, message) {
super(message);
this.name = 'AggregateError';
this.errors = errors;
}
}
async function fetchMultipleResources(urls) {
const errors = [];
const results = [];
for (const url of urls) {
try {
const response = await fetch(url);
results.push(await response.json());
} catch (error) {
errors.push({ url, error });
}
}
if (errors.length > 0) {
throw new AggregateError(errors, `Failed to fetch ${errors.length} resources`);
}
return results;
}
Error Boundaries:
class ErrorHandler {
constructor() {
this.handlers = new Map();
}
onError(errorType, handler) {
this.handlers.set(errorType, handler);
}
handle(error) {
const handler = this.handlers.get(error.name) || this.handlers.get('default');
if (handler) {
handler(error);
} else {
console.error('Unhandled error:', error);
}
}
}
// Usage
const errorHandler = new ErrorHandler();
errorHandler.onError('ValidationError', (error) => {
console.log(`Validation error: ${error.message}`);
});
errorHandler.onError('NetworkError', (error) => {
console.log(`Network error (${error.statusCode}): ${error.message}`);
});
errorHandler.onError('default', (error) => {
console.error('Default error handler:', error);
});
Defensive Programming Techniques
Guard Clauses:
function processUserData(userData) {
// Guard clauses at the top
if (!userData) {
throw new Error('User data is required');
}
if (typeof userData !== 'object') {
throw new TypeError('User data must be an object');
}
if (!userData.id) {
throw new ValidationError('User ID is required', 'id');
}
// Main logic
return processUser(userData);
}
Input Validation:
function validateInput(value, type, required = true) {
if (required && (value === undefined || value === null)) {
throw new ValidationError(`${type} is required`, type);
}
if (value !== undefined && value !== null) {
switch (type) {
case 'string':
if (typeof value !== 'string') {
throw new TypeError(`${type} must be a string`);
}
break;
case 'number':
if (typeof value !== 'number' || isNaN(value)) {
throw new TypeError(`${type} must be a valid number`);
}
break;
case 'array':
if (!Array.isArray(value)) {
throw new TypeError(`${type} must be an array`);
}
break;
}
}
return true;
}
Error Logging and Debugging
Structured Error Logging:
class Logger {
static error(error, context = {}) {
const errorData = {
timestamp: new Date().toISOString(),
name: error.name,
message: error.message,
stack: error.stack,
cause: error.cause,
context
};
console.error(JSON.stringify(errorData, null, 2));
// In a real application, you might send this to a logging service
// sendToLoggingService(errorData);
}
}
// Usage
try {
riskyOperation();
} catch (error) {
Logger.error(error, {
operation: 'riskyOperation',
userId: '12345'
});
}
Error Tracking with Error Codes:
class AppError extends Error {
constructor(message, code, statusCode = 500) {
super(message);
this.name = 'AppError';
this.code = code;
this.statusCode = statusCode;
}
}
const ErrorCodes = {
USER_NOT_FOUND: 'USER_001',
INVALID_INPUT: 'VAL_001',
NETWORK_ERROR: 'NET_001',
DATABASE_ERROR: 'DB_001'
};
function getUserById(id) {
if (!id) {
throw new AppError('User ID is required', ErrorCodes.INVALID_INPUT, 400);
}
const user = findUserInDatabase(id);
if (!user) {
throw new AppError('User not found', ErrorCodes.USER_NOT_FOUND, 404);
}
return user;
}
Changelog
2aa48
-web-deploy(Auto): Update base URL for web-pages branchon
Copyright
Copyright Ownership:WARREN Y.F. LONG
License under:Attribution-NonCommercial-NoDerivatives 4.0 International (CC-BY-NC-ND-4.0)