javascript
examples
examples.js⚡javascript
/**
* ========================================
* 9.4 ERROR HANDLING IN ASYNC CODE - EXAMPLES
* ========================================
*/
// Helper functions
function delay(ms, value) {
return new Promise((resolve) => setTimeout(() => resolve(value), ms));
}
function delayReject(ms, message) {
return new Promise((_, reject) =>
setTimeout(() => reject(new Error(message)), ms)
);
}
/**
* EXAMPLE 1: Basic Promise Error Handling
*/
console.log('--- Example 1: Promise .catch() ---');
Promise.resolve('start')
.then((value) => {
console.log('Step 1:', value);
throw new Error('Step 1 failed!');
})
.then((value) => {
console.log('Step 2:', value); // Never runs
})
.catch((error) => {
console.log('Caught:', error.message);
});
/**
* EXAMPLE 2: Error Recovery in Promise Chain
*/
console.log('\n--- Example 2: Error Recovery ---');
function fetchPrimary() {
return delayReject(50, 'Primary server down');
}
function fetchBackup() {
return delay(50, { source: 'backup', data: [1, 2, 3] });
}
fetchPrimary()
.catch((err) => {
console.log('Primary failed:', err.message);
console.log('Trying backup...');
return fetchBackup();
})
.then((data) => {
console.log('Data received from:', data.source);
});
/**
* EXAMPLE 3: try/catch with async/await
*/
console.log('\n--- Example 3: try/catch ---');
async function fetchWithErrorHandling() {
try {
const data = await delayReject(50, 'Network error');
return data;
} catch (error) {
console.log('Error caught:', error.message);
return { fallback: true };
}
}
fetchWithErrorHandling().then((result) => console.log('Result:', result));
/**
* EXAMPLE 4: Multiple try/catch Blocks
*/
console.log('\n--- Example 4: Multiple try/catch ---');
async function multiStepProcess() {
let step1Result;
// Step 1
try {
step1Result = await delay(50, { value: 10 });
console.log('Step 1 succeeded');
} catch (error) {
console.log('Step 1 failed, using default');
step1Result = { value: 0 };
}
// Step 2
try {
if (step1Result.value === 0) {
throw new Error('Cannot process zero value');
}
const step2Result = await delay(50, step1Result.value * 2);
console.log('Step 2 result:', step2Result);
return step2Result;
} catch (error) {
console.log('Step 2 failed:', error.message);
return null;
}
}
multiStepProcess();
/**
* EXAMPLE 5: Error Tuple Pattern
*/
console.log('\n--- Example 5: Error Tuple Pattern ---');
async function safeAwait(promise) {
try {
const result = await promise;
return [null, result];
} catch (error) {
return [error, null];
}
}
async function useTuplePattern() {
// Success case
const [err1, data1] = await safeAwait(delay(50, 'success'));
if (err1) {
console.log('Error:', err1.message);
} else {
console.log('Data:', data1);
}
// Error case
const [err2, data2] = await safeAwait(delayReject(50, 'failed'));
if (err2) {
console.log('Error:', err2.message);
} else {
console.log('Data:', data2);
}
}
useTuplePattern();
/**
* EXAMPLE 6: Result Object Pattern
*/
console.log('\n--- Example 6: Result Object ---');
async function fetchResult(shouldFail) {
try {
if (shouldFail) {
throw new Error('Simulated failure');
}
const data = await delay(50, { items: [1, 2, 3] });
return { success: true, data, error: null };
} catch (error) {
return { success: false, data: null, error };
}
}
async function useResultPattern() {
const result1 = await fetchResult(false);
if (result1.success) {
console.log('Success:', result1.data);
}
const result2 = await fetchResult(true);
if (!result2.success) {
console.log('Failed:', result2.error.message);
}
}
useResultPattern();
/**
* EXAMPLE 7: Custom Error Types
*/
console.log('\n--- Example 7: Custom Error Types ---');
class NetworkError extends Error {
constructor(message, statusCode) {
super(message);
this.name = 'NetworkError';
this.statusCode = statusCode;
this.retryable = statusCode >= 500;
}
}
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = 'ValidationError';
this.field = field;
}
}
async function simulateRequest(type) {
await delay(50);
switch (type) {
case 'network':
throw new NetworkError('Server unavailable', 503);
case 'validation':
throw new ValidationError('Invalid email', 'email');
case 'success':
return { data: 'ok' };
default:
throw new Error('Unknown error');
}
}
async function handleTypedErrors() {
for (const type of ['success', 'network', 'validation', 'unknown']) {
try {
const result = await simulateRequest(type);
console.log(`${type}: Success`, result);
} catch (error) {
if (error instanceof NetworkError) {
console.log(
`${type}: Network error (${error.statusCode}), retryable: ${error.retryable}`
);
} else if (error instanceof ValidationError) {
console.log(`${type}: Validation error on field '${error.field}'`);
} else {
console.log(`${type}: Unexpected error:`, error.message);
}
}
}
}
handleTypedErrors();
/**
* EXAMPLE 8: Promise.all Error Behavior
*/
console.log('\n--- Example 8: Promise.all Fails Fast ---');
async function demonstratePromiseAll() {
try {
const results = await Promise.all([
delay(100, 'slow success'),
delayReject(50, 'fast failure'),
delay(75, 'medium success'),
]);
console.log('All succeeded:', results);
} catch (error) {
console.log('Promise.all failed:', error.message);
console.log('(Other promises still running but results discarded)');
}
}
demonstratePromiseAll();
/**
* EXAMPLE 9: Promise.allSettled for Graceful Handling
*/
console.log('\n--- Example 9: Promise.allSettled ---');
async function demonstrateAllSettled() {
const results = await Promise.allSettled([
delay(50, 'success 1'),
delayReject(75, 'failure'),
delay(100, 'success 2'),
]);
results.forEach((result, i) => {
if (result.status === 'fulfilled') {
console.log(`Promise ${i}: fulfilled with`, result.value);
} else {
console.log(`Promise ${i}: rejected with`, result.reason.message);
}
});
// Separate successes and failures
const successes = results
.filter((r) => r.status === 'fulfilled')
.map((r) => r.value);
const failures = results
.filter((r) => r.status === 'rejected')
.map((r) => r.reason);
console.log(`${successes.length} succeeded, ${failures.length} failed`);
}
demonstrateAllSettled();
/**
* EXAMPLE 10: Retry with Error Handling
*/
console.log('\n--- Example 10: Retry Pattern ---');
async function retryWithBackoff(fn, maxRetries, baseDelay = 100) {
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 delayMs = baseDelay * Math.pow(2, attempt - 1);
console.log(`Retrying in ${delayMs}ms...`);
await delay(delayMs);
}
}
}
throw new Error(
`All ${maxRetries} attempts failed. Last error: ${lastError.message}`
);
}
let attemptNum = 0;
async function flakyOperation() {
attemptNum++;
if (attemptNum < 3) {
throw new Error(`Attempt ${attemptNum} failed`);
}
return 'Success on attempt ' + attemptNum;
}
retryWithBackoff(flakyOperation, 5, 50)
.then((result) => console.log('Final result:', result))
.catch((error) => console.log('Final error:', error.message));
/**
* EXAMPLE 11: Error Aggregation
*/
console.log('\n--- Example 11: Error Aggregation ---');
class AggregateError extends Error {
constructor(errors, message = 'Multiple errors occurred') {
super(message);
this.name = 'AggregateError';
this.errors = errors;
}
}
async function runWithErrorCollection(tasks) {
const results = [];
const errors = [];
for (const task of tasks) {
try {
results.push(await task());
} catch (error) {
errors.push(error);
}
}
if (errors.length > 0) {
console.log(`Completed with ${errors.length} errors:`);
errors.forEach((e, i) => console.log(` ${i + 1}. ${e.message}`));
}
return { results, errors };
}
runWithErrorCollection([
() => delay(50, 'Task 1 OK'),
() => delayReject(50, 'Task 2 Failed'),
() => delay(50, 'Task 3 OK'),
() => delayReject(50, 'Task 4 Failed'),
]).then(({ results, errors }) => {
console.log('Successful results:', results);
});
/**
* EXAMPLE 12: finally for Cleanup
*/
console.log('\n--- Example 12: finally Cleanup ---');
async function withCleanup(shouldFail) {
console.log('Acquiring resource...');
const resource = { id: Date.now(), active: true };
try {
if (shouldFail) {
throw new Error('Operation failed');
}
await delay(50);
console.log('Operation succeeded');
return 'success';
} catch (error) {
console.log('Operation failed:', error.message);
throw error;
} finally {
// Always runs
console.log('Releasing resource...');
resource.active = false;
}
}
// Success case
withCleanup(false).then((r) => console.log('Result:', r));
// Failure case
delay(200).then(() => {
withCleanup(true).catch((e) => console.log('Caught in caller:', e.message));
});
/**
* EXAMPLE 13: Resource Manager with Cleanup
*/
console.log('\n--- Example 13: Resource Manager ---');
class Connection {
constructor(id) {
this.id = id;
this.open = true;
}
async query(sql) {
if (!this.open) throw new Error('Connection closed');
await delay(50);
return [`Result for: ${sql}`];
}
async close() {
this.open = false;
console.log(`Connection ${this.id} closed`);
}
}
class ConnectionPool {
static nextId = 0;
static async getConnection() {
const conn = new Connection(++this.nextId);
console.log(`Connection ${conn.id} opened`);
return conn;
}
static async use(asyncFn) {
const connection = await this.getConnection();
try {
return await asyncFn(connection);
} finally {
await connection.close();
}
}
}
async function demonstrateResourceManager() {
// Connection always closed, even on error
try {
const result = await ConnectionPool.use(async (conn) => {
const data = await conn.query('SELECT * FROM users');
// throw new Error("Processing error"); // Uncomment to test cleanup on error
return data;
});
console.log('Query result:', result);
} catch (error) {
console.log('Error handled:', error.message);
}
}
demonstrateResourceManager();
/**
* EXAMPLE 14: Error Context Enhancement
*/
console.log('\n--- Example 14: Error Context ---');
class ContextualError extends Error {
constructor(message, context = {}) {
super(message);
this.context = context;
this.timestamp = new Date().toISOString();
}
toJSON() {
return {
message: this.message,
context: this.context,
timestamp: this.timestamp,
stack: this.stack,
};
}
}
async function fetchUser(userId) {
try {
await delay(50);
throw new Error('Database connection failed');
} catch (error) {
throw new ContextualError(`Failed to fetch user ${userId}`, {
userId,
operation: 'fetchUser',
originalError: error.message,
});
}
}
async function demonstrateContext() {
try {
await fetchUser(123);
} catch (error) {
console.log('Error with context:', JSON.stringify(error.toJSON(), null, 2));
}
}
delay(400).then(() => demonstrateContext());
/**
* EXAMPLE 15: Circuit Breaker Pattern
*/
console.log('\n--- Example 15: Circuit Breaker ---');
class CircuitBreaker {
constructor(options = {}) {
this.failureThreshold = options.failureThreshold || 3;
this.resetTimeout = options.resetTimeout || 1000;
this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
this.failures = 0;
this.lastFailure = null;
}
async call(asyncFn) {
if (this.state === 'OPEN') {
if (Date.now() - this.lastFailure > this.resetTimeout) {
this.state = 'HALF_OPEN';
console.log('Circuit: HALF_OPEN (testing)');
} else {
throw new Error('Circuit breaker is OPEN');
}
}
try {
const result = await asyncFn();
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: CLOSED (recovered)');
}
}
onFailure() {
this.failures++;
this.lastFailure = Date.now();
if (this.failures >= this.failureThreshold) {
this.state = 'OPEN';
console.log('Circuit: OPEN (too many failures)');
}
}
}
async function demonstrateCircuitBreaker() {
const breaker = new CircuitBreaker({
failureThreshold: 2,
resetTimeout: 200,
});
const unreliableService = async () => {
await delay(20);
if (Math.random() < 0.8) throw new Error('Service error');
return 'Success';
};
for (let i = 0; i < 6; i++) {
try {
const result = await breaker.call(unreliableService);
console.log(`Call ${i + 1}: ${result}`);
} catch (error) {
console.log(`Call ${i + 1}: ${error.message}`);
}
await delay(100);
}
}
delay(600).then(() => demonstrateCircuitBreaker());
/**
* EXAMPLE 16: Global Unhandled Rejection Handler
*/
console.log('\n--- Example 16: Global Handler (Node.js) ---');
// In Node.js environment:
if (typeof process !== 'undefined') {
process.on('unhandledRejection', (reason, promise) => {
console.log('Unhandled rejection detected!');
console.log('Reason:', reason);
// In production: log to monitoring service
});
}
// Intentionally unhandled (for demonstration)
// Promise.reject(new Error("Unhandled!"));
console.log('Global handler registered');
/**
* EXAMPLE 17: Error Handling Middleware Pattern
*/
console.log('\n--- Example 17: Error Middleware ---');
class AsyncPipeline {
constructor() {
this.middleware = [];
this.errorHandlers = [];
}
use(fn) {
this.middleware.push(fn);
return this;
}
onError(handler) {
this.errorHandlers.push(handler);
return this;
}
async execute(context) {
try {
for (const fn of this.middleware) {
context = await fn(context);
}
return context;
} catch (error) {
for (const handler of this.errorHandlers) {
try {
return await handler(error, context);
} catch (e) {
// Handler failed, try next
continue;
}
}
throw error; // No handler succeeded
}
}
}
async function demonstratePipeline() {
const pipeline = new AsyncPipeline()
.use(async (ctx) => {
await delay(20);
return { ...ctx, step1: true };
})
.use(async (ctx) => {
if (ctx.shouldFail) throw new Error('Pipeline error');
return { ...ctx, step2: true };
})
.onError(async (error, ctx) => {
console.log('Error handler caught:', error.message);
return { ...ctx, recovered: true };
});
// Success
const result1 = await pipeline.execute({ shouldFail: false });
console.log('Pipeline success:', result1);
// With error recovery
const result2 = await pipeline.execute({ shouldFail: true });
console.log('Pipeline recovered:', result2);
}
delay(1200).then(() => demonstratePipeline());
/**
* EXAMPLE 18: Timeout with Cleanup
*/
console.log('\n--- Example 18: Timeout with Cleanup ---');
async function withTimeoutAndCleanup(asyncFn, timeoutMs, cleanup) {
let timeoutId;
let completed = false;
const timeoutPromise = new Promise((_, reject) => {
timeoutId = setTimeout(() => {
if (!completed) {
reject(new Error(`Timeout after ${timeoutMs}ms`));
}
}, timeoutMs);
});
try {
const result = await Promise.race([asyncFn(), timeoutPromise]);
completed = true;
return result;
} catch (error) {
if (cleanup) await cleanup();
throw error;
} finally {
clearTimeout(timeoutId);
}
}
async function demonstrateTimeoutCleanup() {
try {
await withTimeoutAndCleanup(
() => delay(100, 'result'),
50,
() => console.log('Cleanup after timeout')
);
} catch (error) {
console.log('Caught:', error.message);
}
}
delay(1400).then(() => demonstrateTimeoutCleanup());
/**
* EXAMPLE 19: Async Error Boundary
*/
console.log('\n--- Example 19: Error Boundary ---');
function createErrorBoundary(options = {}) {
const {
onError = console.error,
fallback = null,
retry = false,
maxRetries = 3,
} = options;
return async function (asyncFn) {
let attempts = 0;
while (attempts < (retry ? maxRetries : 1)) {
try {
attempts++;
return await asyncFn();
} catch (error) {
onError(error, attempts);
if (!retry || attempts >= maxRetries) {
return typeof fallback === 'function' ? fallback(error) : fallback;
}
await delay(100 * attempts); // Backoff
}
}
};
}
async function demonstrateErrorBoundary() {
const safeFetch = createErrorBoundary({
onError: (err, attempt) =>
console.log(`Attempt ${attempt} failed:`, err.message),
fallback: { data: 'default' },
retry: true,
maxRetries: 2,
});
const result = await safeFetch(async () => {
throw new Error('Always fails');
});
console.log('Boundary result:', result);
}
delay(1600).then(() => demonstrateErrorBoundary());
/**
* EXAMPLE 20: Complete Error Handling Strategy
*/
console.log('\n--- Example 20: Complete Strategy ---');
// Error types
class AppError extends Error {
constructor(message, code, recoverable = false) {
super(message);
this.code = code;
this.recoverable = recoverable;
}
}
// Error logger
const errorLogger = {
log(error, context) {
console.log(`[ERROR] ${error.code || 'UNKNOWN'}: ${error.message}`);
if (context) console.log(`[CONTEXT] ${JSON.stringify(context)}`);
},
};
// API service with comprehensive error handling
class APIService {
constructor(baseUrl) {
this.baseUrl = baseUrl;
this.retryConfig = { maxRetries: 3, baseDelay: 100 };
}
async request(endpoint, options = {}) {
const { retries = this.retryConfig.maxRetries } = options;
for (let attempt = 1; attempt <= retries; attempt++) {
try {
return await this.#makeRequest(endpoint);
} catch (error) {
const isLastAttempt = attempt === retries;
// Log error
errorLogger.log(error, { endpoint, attempt, isLastAttempt });
// Don't retry non-recoverable errors
if (error instanceof AppError && !error.recoverable) {
throw error;
}
if (isLastAttempt) {
throw new AppError(
`Request failed after ${retries} attempts`,
'MAX_RETRIES_EXCEEDED'
);
}
// Wait before retry
await delay(this.retryConfig.baseDelay * attempt);
}
}
}
async #makeRequest(endpoint) {
await delay(50);
// Simulate various errors
const rand = Math.random();
if (rand < 0.3) {
throw new AppError('Network error', 'NETWORK_ERROR', true);
} else if (rand < 0.4) {
throw new AppError('Unauthorized', 'AUTH_ERROR', false);
}
return { data: `Response from ${endpoint}` };
}
async safeRequest(endpoint) {
try {
return await this.request(endpoint);
} catch (error) {
if (error.code === 'AUTH_ERROR') {
return { error: 'Please login again', redirect: '/login' };
}
return { error: error.message, fallback: true };
}
}
}
async function demonstrateCompleteStrategy() {
const api = new APIService('https://api.example.com');
// Multiple requests with graceful degradation
const results = await Promise.allSettled([
api.safeRequest('/users'),
api.safeRequest('/posts'),
api.safeRequest('/comments'),
]);
console.log('\nFinal Results:');
results.forEach((result, i) => {
const endpoints = ['/users', '/posts', '/comments'];
console.log(
`${endpoints[i]}: ${JSON.stringify(
result.value || result.reason?.message
)}`
);
});
}
delay(1800).then(() => demonstrateCompleteStrategy());
console.log('\n========================================');
console.log('End of Error Handling Examples');
console.log('(Results will appear asynchronously)');
console.log('========================================');