Custom error classes and error handling utilities for robust application development. Provides structured error types and functional error handling patterns.
Specialized error classes for common error scenarios in asynchronous and operational contexts.
/**
* Error representing aborted operations
*/
class AbortError extends Error {
/**
* Creates new AbortError
* @param message - Error message (default: 'The operation was aborted')
*/
constructor(message?: string);
/**
* Error name identifier
*/
name: 'AbortError';
}
/**
* Error representing timed out operations
*/
class TimeoutError extends Error {
/**
* Creates new TimeoutError
* @param message - Error message (default: 'The operation was timed out')
*/
constructor(message?: string);
/**
* Error name identifier
*/
name: 'TimeoutError';
}Usage Examples:
import { AbortError, TimeoutError } from 'es-toolkit/error';
// Abortable operations
async function fetchWithAbort(url: string, signal?: AbortSignal): Promise<Response> {
if (signal?.aborted) {
throw new AbortError('Request was aborted before starting');
}
try {
const response = await fetch(url, { signal });
return response;
} catch (error) {
if (error.name === 'AbortError') {
throw new AbortError(`Request to ${url} was aborted`);
}
throw error;
}
}
// Timeout operations
async function operationWithTimeout<T>(
operation: () => Promise<T>,
timeoutMs: number
): Promise<T> {
return Promise.race([
operation(),
new Promise<never>((_, reject) => {
setTimeout(() => {
reject(new TimeoutError(`Operation timed out after ${timeoutMs}ms`));
}, timeoutMs);
})
]);
}
// Usage examples
async function examples() {
const controller = new AbortController();
// Abort after 2 seconds
setTimeout(() => controller.abort(), 2000);
try {
const response = await fetchWithAbort(
'https://api.example.com/slow-endpoint',
controller.signal
);
console.log('Request completed');
} catch (error) {
if (error instanceof AbortError) {
console.log('Request was cancelled:', error.message);
} else {
console.log('Request failed:', error.message);
}
}
// Timeout example
try {
const result = await operationWithTimeout(
() => new Promise(resolve => setTimeout(() => resolve('done'), 5000)),
3000
);
console.log('Operation result:', result);
} catch (error) {
if (error instanceof TimeoutError) {
console.log('Operation timed out:', error.message);
}
}
}Utilities for handling errors using functional programming patterns, avoiding traditional try-catch blocks.
/**
* Executes function and returns result or error as tuple (synchronous)
* @param func - Function to execute
* @returns Tuple of [error, null] or [null, result]
*/
function attempt<T, E = Error>(func: () => T): [E, null] | [null, T];
/**
* Executes async function and returns result or error as tuple (asynchronous)
* @param func - Async function to execute
* @returns Promise resolving to tuple of [error, null] or [null, result]
*/
function attemptAsync<T, E = Error>(func: () => Promise<T>): Promise<[E, null] | [null, T]>;
/**
* Asserts that condition is truthy, throws error if false
* @param condition - Condition to check
* @param message - Error message or Error instance to throw
* @throws Error if condition is falsy
*/
function invariant(condition: unknown, message: string | Error): asserts condition;
/**
* Alias for invariant function
* @param condition - Condition to check
* @param message - Error message or Error instance to throw
* @throws Error if condition is falsy
*/
function assert(condition: unknown, message: string | Error): asserts condition;Usage Examples:
import { attempt, attemptAsync, invariant, assert } from 'es-toolkit/error';
// Synchronous error handling with attempt
function parseConfig(configText: string) {
const [error, config] = attempt(() => JSON.parse(configText));
if (error) {
console.error('Failed to parse config:', error.message);
return null;
}
// TypeScript knows config is the parsed result here
return config;
}
// Multiple operations with error handling
function processUserData(userData: string) {
// Parse JSON
const [parseError, data] = attempt(() => JSON.parse(userData));
if (parseError) {
return { success: false, error: 'Invalid JSON format' };
}
// Validate required fields
const [validationError] = attempt(() => {
invariant(data.name, 'Name is required');
invariant(data.email, 'Email is required');
invariant(typeof data.age === 'number', 'Age must be a number');
});
if (validationError) {
return { success: false, error: validationError.message };
}
return { success: true, data };
}
// Asynchronous error handling
async function fetchUserProfile(userId: string) {
const [error, response] = await attemptAsync(async () => {
const res = await fetch(`/api/users/${userId}`);
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
}
return res.json();
});
if (error) {
console.error('Failed to fetch user profile:', error.message);
return null;
}
return response;
}
// Database operations with error handling
async function createUser(userData: any) {
// Validate input
const [validationError] = attempt(() => {
assert(userData.email, 'Email is required');
assert(userData.name, 'Name is required');
assert(userData.email.includes('@'), 'Invalid email format');
});
if (validationError) {
return { success: false, error: validationError.message };
}
// Attempt database insertion
const [dbError, user] = await attemptAsync(async () => {
const result = await database.users.create(userData);
return result;
});
if (dbError) {
return { success: false, error: 'Database error: ' + dbError.message };
}
return { success: true, data: user };
}
// File operations
async function readConfigFile(filePath: string) {
const [readError, content] = await attemptAsync(() =>
fs.promises.readFile(filePath, 'utf8')
);
if (readError) {
return { error: `Failed to read file: ${readError.message}` };
}
const [parseError, config] = attempt(() => JSON.parse(content));
if (parseError) {
return { error: `Invalid JSON in config file: ${parseError.message}` };
}
return { config };
}
// Usage in pipeline
async function userRegistrationPipeline(registrationData: any) {
// Step 1: Validate input
const [validationError] = attempt(() => {
invariant(registrationData.email, 'Email is required');
invariant(registrationData.password, 'Password is required');
invariant(registrationData.password.length >= 8, 'Password must be at least 8 characters');
});
if (validationError) {
return { step: 'validation', error: validationError.message };
}
// Step 2: Check if user exists
const [checkError, existingUser] = await attemptAsync(() =>
database.users.findByEmail(registrationData.email)
);
if (checkError) {
return { step: 'user-check', error: checkError.message };
}
if (existingUser) {
return { step: 'user-check', error: 'User already exists' };
}
// Step 3: Hash password
const [hashError, hashedPassword] = await attemptAsync(() =>
bcrypt.hash(registrationData.password, 10)
);
if (hashError) {
return { step: 'password-hash', error: hashError.message };
}
// Step 4: Create user
const [createError, user] = await attemptAsync(() =>
database.users.create({
...registrationData,
password: hashedPassword
})
);
if (createError) {
return { step: 'user-creation', error: createError.message };
}
return { success: true, user };
}import { attempt, attemptAsync } from 'es-toolkit/error';
type Result<T, E = Error> = {
success: true;
data: T;
} | {
success: false;
error: E;
};
function createResult<T, E = Error>(
attemptResult: [E, null] | [null, T]
): Result<T, E> {
const [error, data] = attemptResult;
if (error) {
return { success: false, error };
}
return { success: true, data };
}
async function safeApiCall(url: string): Promise<Result<any>> {
const result = await attemptAsync(async () => {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
});
return createResult(result);
}
// Usage
async function handleApiCall() {
const result = await safeApiCall('/api/data');
if (result.success) {
console.log('Data:', result.data);
} else {
console.error('Error:', result.error.message);
}
}import { attempt, invariant } from 'es-toolkit/error';
class ValidationError extends Error {
constructor(public errors: string[]) {
super(`Validation failed: ${errors.join(', ')}`);
this.name = 'ValidationError';
}
}
function validateForm(formData: any): ValidationError | null {
const errors: string[] = [];
// Collect all validation errors
const validations = [
() => invariant(formData.name, 'Name is required'),
() => invariant(formData.email, 'Email is required'),
() => invariant(formData.email.includes('@'), 'Invalid email format'),
() => invariant(formData.age >= 18, 'Must be at least 18 years old'),
() => invariant(formData.password, 'Password is required'),
() => invariant(formData.password.length >= 8, 'Password must be at least 8 characters')
];
validations.forEach(validation => {
const [error] = attempt(validation);
if (error) {
errors.push(error.message);
}
});
return errors.length > 0 ? new ValidationError(errors) : null;
}
// Usage
function processForm(formData: any) {
const validationError = validateForm(formData);
if (validationError) {
return {
success: false,
errors: validationError.errors
};
}
return { success: true };
}import { attemptAsync, TimeoutError } from 'es-toolkit/error';
async function retryWithBackoff<T>(
operation: () => Promise<T>,
maxRetries: number = 3,
baseDelayMs: number = 1000,
maxDelayMs: number = 30000
): Promise<T> {
let lastError: Error;
for (let attempt = 0; attempt < maxRetries; attempt++) {
const [error, result] = await attemptAsync(operation);
if (!error) {
return result;
}
lastError = error;
// Don't retry on certain errors
if (error instanceof AbortError || error instanceof TimeoutError) {
throw error;
}
if (attempt < maxRetries - 1) {
const delay = Math.min(baseDelayMs * Math.pow(2, attempt), maxDelayMs);
const jitter = delay * 0.1 * Math.random(); // Add jitter
console.log(`Attempt ${attempt + 1} failed, retrying in ${delay + jitter}ms...`);
await new Promise(resolve => setTimeout(resolve, delay + jitter));
}
}
throw lastError!;
}
// Usage
async function reliableFetch(url: string) {
return retryWithBackoff(
async () => {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
},
3, // max retries
1000, // base delay: 1 second
10000 // max delay: 10 seconds
);
}import { attempt, attemptAsync } from 'es-toolkit/error';
class CircuitBreakerError extends Error {
constructor(message: string) {
super(message);
this.name = 'CircuitBreakerError';
}
}
class CircuitBreaker<T> {
private failures = 0;
private lastFailureTime = 0;
private state: 'CLOSED' | 'OPEN' | 'HALF_OPEN' = 'CLOSED';
constructor(
private operation: () => Promise<T>,
private failureThreshold: number = 5,
private resetTimeoutMs: number = 60000
) {}
async execute(): Promise<T> {
const [stateError] = attempt(() => this.checkState());
if (stateError) {
throw stateError;
}
const [operationError, result] = await attemptAsync(this.operation);
if (operationError) {
this.recordFailure();
throw operationError;
}
this.recordSuccess();
return result;
}
private checkState(): void {
if (this.state === 'OPEN') {
if (Date.now() - this.lastFailureTime < this.resetTimeoutMs) {
throw new CircuitBreakerError('Circuit breaker is OPEN');
} else {
this.state = 'HALF_OPEN';
}
}
}
private recordFailure(): void {
this.failures++;
this.lastFailureTime = Date.now();
if (this.failures >= this.failureThreshold) {
this.state = 'OPEN';
}
}
private recordSuccess(): void {
this.failures = 0;
this.state = 'CLOSED';
}
}
// Usage
const apiCircuitBreaker = new CircuitBreaker(
() => fetch('/api/unstable-endpoint').then(r => r.json()),
3, // failure threshold
30000 // reset timeout: 30 seconds
);
async function callApi() {
const [error, result] = await attemptAsync(() => apiCircuitBreaker.execute());
if (error) {
if (error instanceof CircuitBreakerError) {
console.log('Service temporarily unavailable');
return null;
} else {
console.log('API call failed:', error.message);
return null;
}
}
return result;
}import { attempt, attemptAsync } from 'es-toolkit/error';
interface ErrorContext {
operation: string;
userId?: string;
requestId?: string;
timestamp: number;
metadata?: Record<string, any>;
}
class ErrorLogger {
static log(error: Error, context: ErrorContext): void {
const logEntry = {
error: {
name: error.name,
message: error.message,
stack: error.stack
},
context,
severity: this.getSeverity(error)
};
console.error('Application Error:', JSON.stringify(logEntry, null, 2));
// Send to monitoring service
this.sendToMonitoring(logEntry);
}
private static getSeverity(error: Error): 'low' | 'medium' | 'high' | 'critical' {
if (error instanceof AbortError) return 'low';
if (error instanceof TimeoutError) return 'medium';
if (error.name === 'ValidationError') return 'low';
if (error.message.includes('database')) return 'high';
return 'medium';
}
private static sendToMonitoring(logEntry: any): void {
// Implementation for sending to monitoring service
// This would typically be async but we don't want to block
setImmediate(() => {
attempt(() => {
// Send to monitoring service
console.log('Sent to monitoring:', logEntry);
});
});
}
}
// Wrapper function for operations with automatic error logging
async function withErrorLogging<T>(
operation: () => Promise<T>,
context: Omit<ErrorContext, 'timestamp'>
): Promise<T | null> {
const [error, result] = await attemptAsync(operation);
if (error) {
ErrorLogger.log(error, {
...context,
timestamp: Date.now()
});
return null;
}
return result;
}
// Usage
async function userService(userId: string, requestId: string) {
const user = await withErrorLogging(
() => database.users.findById(userId),
{
operation: 'fetch-user',
userId,
requestId
}
);
if (!user) {
return null;
}
const profile = await withErrorLogging(
() => database.profiles.findByUserId(userId),
{
operation: 'fetch-user-profile',
userId,
requestId,
metadata: { userEmail: user.email }
}
);
return { user, profile };
}import { invariant } from 'es-toolkit/error';
// Define domain-specific error types
class ValidationError extends Error {
constructor(field: string, message: string) {
super(`Validation error for ${field}: ${message}`);
this.name = 'ValidationError';
}
}
class BusinessLogicError extends Error {
constructor(message: string) {
super(message);
this.name = 'BusinessLogicError';
}
}
class ExternalServiceError extends Error {
constructor(service: string, message: string) {
super(`External service error (${service}): ${message}`);
this.name = 'ExternalServiceError';
}
}
// Use consistent error handling patterns
function validateUser(userData: any): void {
invariant(userData.email, new ValidationError('email', 'Email is required'));
invariant(userData.age >= 18, new BusinessLogicError('User must be at least 18 years old'));
}