javascript
examples
examples.js⚡javascript
/**
* Advanced Error Handling - Examples
* Comprehensive patterns for async error handling
*/
// =============================================================================
// 1. BASIC TRY-CATCH WITH ASYNC/AWAIT
// =============================================================================
console.log('--- Basic Try-Catch ---');
// Simulated async operations
function simulateFetch(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (url.includes('fail')) {
reject(new Error(`Failed to fetch: ${url}`));
} else {
resolve({ data: 'Success', url });
}
}, 100);
});
}
async function basicTryCatch() {
try {
const result = await simulateFetch('/api/data');
console.log('Success:', result);
} catch (error) {
console.log('Caught error:', error.message);
} finally {
console.log('Cleanup: always runs');
}
}
// Run example
basicTryCatch();
// =============================================================================
// 2. CUSTOM ERROR CLASSES
// =============================================================================
console.log('\n--- Custom Error Classes ---');
class NetworkError extends Error {
constructor(message, statusCode, url) {
super(message);
this.name = 'NetworkError';
this.statusCode = statusCode;
this.url = url;
this.timestamp = new Date();
}
toJSON() {
return {
name: this.name,
message: this.message,
statusCode: this.statusCode,
url: this.url,
timestamp: this.timestamp,
};
}
}
class ValidationError extends Error {
constructor(field, value, constraint) {
super(`Validation failed for ${field}: ${constraint}`);
this.name = 'ValidationError';
this.field = field;
this.value = value;
this.constraint = constraint;
}
}
class TimeoutError extends Error {
constructor(operation, duration) {
super(`Operation "${operation}" timed out after ${duration}ms`);
this.name = 'TimeoutError';
this.operation = operation;
this.duration = duration;
}
}
class RetryableError extends Error {
constructor(message, retryAfter = 1000) {
super(message);
this.name = 'RetryableError';
this.retryAfter = retryAfter;
this.retryable = true;
}
}
// Using custom errors
function fetchWithCustomError(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (url.includes('404')) {
reject(new NetworkError('Not Found', 404, url));
} else if (url.includes('429')) {
reject(new RetryableError('Rate limited', 2000));
} else if (url.includes('validation')) {
reject(new ValidationError('email', 'invalid@', 'must be valid email'));
} else {
resolve({ data: 'OK' });
}
}, 100);
});
}
async function handleCustomErrors() {
const urls = ['/api/404', '/api/429', '/api/validation'];
for (const url of urls) {
try {
await fetchWithCustomError(url);
} catch (error) {
if (error instanceof NetworkError) {
console.log(`NetworkError: ${error.statusCode} at ${error.url}`);
} else if (error instanceof RetryableError) {
console.log(`RetryableError: retry after ${error.retryAfter}ms`);
} else if (error instanceof ValidationError) {
console.log(`ValidationError: ${error.field} - ${error.constraint}`);
} else {
console.log('Unknown error:', error.message);
}
}
}
}
setTimeout(handleCustomErrors, 200);
// =============================================================================
// 3. ERROR RECOVERY PATTERNS
// =============================================================================
console.log('\n--- Error Recovery Patterns ---');
// Fallback pattern
async function fetchWithFallback(primary, fallback) {
try {
return await simulateFetch(primary);
} catch (primaryError) {
console.log(`Primary failed: ${primaryError.message}, trying fallback...`);
return await simulateFetch(fallback);
}
}
// Default value pattern
async function fetchWithDefault(url, defaultValue) {
try {
return await simulateFetch(url);
} catch {
console.log('Using default value');
return defaultValue;
}
}
// Run recovery examples
setTimeout(async () => {
console.log('\n--- Fallback Pattern ---');
const result = await fetchWithFallback('/api/fail', '/api/success');
console.log('Final result:', result);
console.log('\n--- Default Value Pattern ---');
const result2 = await fetchWithDefault('/api/fail', { data: 'default' });
console.log('Result with default:', result2);
}, 500);
// =============================================================================
// 4. RETRY PATTERN
// =============================================================================
console.log('\n--- Retry Pattern ---');
async function withRetry(fn, maxRetries = 3, delay = 100, backoff = 2) {
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error;
console.log(`Attempt ${attempt} failed: ${error.message}`);
if (attempt < maxRetries) {
const waitTime = delay * Math.pow(backoff, attempt - 1);
console.log(`Waiting ${waitTime}ms before retry...`);
await new Promise((resolve) => setTimeout(resolve, waitTime));
}
}
}
throw new Error(
`All ${maxRetries} attempts failed. Last error: ${lastError.message}`
);
}
// Retry with conditional
async function withConditionalRetry(fn, shouldRetry, maxRetries = 3) {
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error;
if (!shouldRetry(error) || attempt === maxRetries) {
throw error;
}
console.log(`Retrying due to: ${error.message}`);
await new Promise((r) => setTimeout(r, 100));
}
}
throw lastError;
}
// Demonstrate retry
let attempts = 0;
const flakeyOperation = () =>
new Promise((resolve, reject) => {
attempts++;
if (attempts < 3) {
reject(new Error(`Attempt ${attempts} failed`));
} else {
resolve('Success on attempt ' + attempts);
}
});
setTimeout(async () => {
console.log('\n--- Retry Demonstration ---');
try {
const result = await withRetry(flakeyOperation, 5, 50, 1);
console.log('Final result:', result);
} catch (error) {
console.log('All retries failed:', error.message);
}
}, 1000);
// =============================================================================
// 5. MULTIPLE ERROR HANDLING LEVELS
// =============================================================================
console.log('\n--- Multiple Error Levels ---');
class DataService {
// Low-level: specific error handling
async fetchRaw(url) {
try {
const response = await simulateFetch(url);
return response;
} catch (error) {
// Transform to domain error
throw new NetworkError(
`Failed to fetch data: ${error.message}`,
500,
url
);
}
}
// Mid-level: business logic errors
async getUser(id) {
try {
const data = await this.fetchRaw(`/users/${id}`);
if (!data.data) {
throw new ValidationError('user', id, 'must exist');
}
return data;
} catch (error) {
if (error instanceof NetworkError) {
// Let network errors bubble up
throw error;
}
// Wrap other errors
throw new Error(`User retrieval failed: ${error.message}`);
}
}
// High-level: user-facing error handling
async getUserSafely(id) {
try {
return await this.getUser(id);
} catch (error) {
console.log('Service error:', error.constructor.name, '-', error.message);
return null; // Return safe default
}
}
}
const service = new DataService();
setTimeout(async () => {
console.log('\n--- Service Error Handling ---');
const user = await service.getUserSafely(123);
console.log('User result:', user);
}, 1500);
// =============================================================================
// 6. AGGREGATE ERROR HANDLING
// =============================================================================
console.log('\n--- Aggregate Error Handling ---');
async function collectErrors(operations) {
const errors = [];
const results = [];
for (const op of operations) {
try {
results.push(await op());
} catch (error) {
errors.push(error);
}
}
return { results, errors };
}
// Handle all results with Promise.allSettled
async function handleAllResults(promises) {
const results = await Promise.allSettled(promises);
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,
hasErrors: failed.length > 0,
allSucceeded: failed.length === 0,
};
}
setTimeout(async () => {
console.log('\n--- Aggregate Results ---');
const operations = [
simulateFetch('/api/1'),
simulateFetch('/api/fail'),
simulateFetch('/api/3'),
];
const { successful, failed, hasErrors } = await handleAllResults(operations);
console.log(`Successful: ${successful.length}, Failed: ${failed.length}`);
console.log('Has errors:', hasErrors);
}, 2000);
// =============================================================================
// 7. ERROR WRAPPING AND CONTEXT
// =============================================================================
console.log('\n--- Error Wrapping ---');
class ContextualError extends Error {
constructor(message, cause, context = {}) {
super(message);
this.name = 'ContextualError';
this.cause = cause;
this.context = context;
this.timestamp = new Date();
}
getFullStack() {
let stack = this.stack;
if (this.cause instanceof Error) {
stack += '\nCaused by: ' + this.cause.stack;
}
return stack;
}
}
function wrapError(error, message, context = {}) {
return new ContextualError(message, error, {
...context,
originalMessage: error.message,
});
}
async function processWithContext() {
try {
try {
await simulateFetch('/api/fail');
} catch (lowLevelError) {
throw wrapError(lowLevelError, 'Failed to process user data', {
userId: 123,
action: 'fetch',
});
}
} catch (error) {
console.log('Error message:', error.message);
console.log('Original cause:', error.cause?.message);
console.log('Context:', error.context);
}
}
setTimeout(processWithContext, 2500);
// =============================================================================
// 8. ASYNC ERROR BOUNDARY
// =============================================================================
console.log('\n--- Error Boundary ---');
function createErrorBoundary(handler) {
return function (target, key, descriptor) {
const original = descriptor.value;
descriptor.value = async function (...args) {
try {
return await original.apply(this, args);
} catch (error) {
return handler(error, { method: key, args });
}
};
return descriptor;
};
}
// Manual error boundary wrapper
function withErrorBoundary(fn, errorHandler) {
return async (...args) => {
try {
return await fn(...args);
} catch (error) {
return errorHandler(error, args);
}
};
}
const safeFetch = withErrorBoundary(
async (url) => {
const result = await simulateFetch(url);
return result;
},
(error, args) => {
console.log(`Error boundary caught: ${error.message} for ${args[0]}`);
return { error: true, message: error.message };
}
);
setTimeout(async () => {
console.log('\n--- Error Boundary Demo ---');
const result1 = await safeFetch('/api/success');
console.log('Result 1:', result1);
const result2 = await safeFetch('/api/fail');
console.log('Result 2:', result2);
}, 3000);
// =============================================================================
// 9. CLEANUP AND RESOURCE MANAGEMENT
// =============================================================================
console.log('\n--- Resource Cleanup ---');
class ResourceManager {
constructor() {
this.resources = [];
}
async acquire(name) {
console.log(`Acquiring: ${name}`);
this.resources.push(name);
return { name, acquired: true };
}
async release(name) {
console.log(`Releasing: ${name}`);
this.resources = this.resources.filter((r) => r !== name);
}
async releaseAll() {
console.log('Releasing all resources...');
for (const resource of [...this.resources]) {
await this.release(resource);
}
}
}
async function withResources(fn) {
const manager = new ResourceManager();
try {
return await fn(manager);
} catch (error) {
console.log('Error during operation:', error.message);
throw error;
} finally {
await manager.releaseAll();
}
}
setTimeout(async () => {
console.log('\n--- Resource Management Demo ---');
try {
await withResources(async (manager) => {
await manager.acquire('database');
await manager.acquire('cache');
// Simulate error
throw new Error('Operation failed');
});
} catch (error) {
console.log('Handled:', error.message);
}
}, 3500);
// =============================================================================
// 10. CIRCUIT BREAKER PATTERN
// =============================================================================
console.log('\n--- Circuit Breaker ---');
class CircuitBreaker {
constructor(threshold = 3, timeout = 5000) {
this.failures = 0;
this.threshold = threshold;
this.timeout = timeout;
this.state = 'CLOSED'; // CLOSED, OPEN, HALF-OPEN
this.nextAttempt = null;
}
async execute(fn) {
if (this.state === 'OPEN') {
if (Date.now() > this.nextAttempt) {
this.state = 'HALF-OPEN';
console.log('Circuit breaker: 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;
if (this.state === 'HALF-OPEN') {
this.state = 'CLOSED';
console.log('Circuit breaker: CLOSED (recovered)');
}
}
onFailure() {
this.failures++;
console.log(`Circuit breaker: failure ${this.failures}/${this.threshold}`);
if (this.failures >= this.threshold) {
this.state = 'OPEN';
this.nextAttempt = Date.now() + this.timeout;
console.log(
`Circuit breaker: OPEN (will retry at ${new Date(
this.nextAttempt
).toISOString()})`
);
}
}
}
const breaker = new CircuitBreaker(2, 1000);
const failingOp = () => simulateFetch('/api/fail');
setTimeout(async () => {
console.log('\n--- Circuit Breaker Demo ---');
for (let i = 0; i < 4; i++) {
try {
await breaker.execute(failingOp);
} catch (error) {
console.log('Error:', error.message);
}
await new Promise((r) => setTimeout(r, 100));
}
}, 4000);