|
| 1 | +/** |
| 2 | + * Error Handler Module |
| 3 | + * Provides centralised error handling and recovery mechanisms |
| 4 | + */ |
| 5 | + |
| 6 | +import { logger } from './logger.js'; |
| 7 | +import { showNotification } from './ui.js'; |
| 8 | + |
| 9 | +/** |
| 10 | + * Error types |
| 11 | + */ |
| 12 | +export const ERROR_TYPES = { |
| 13 | + NETWORK: 'NETWORK_ERROR', |
| 14 | + DATA_LOAD: 'DATA_LOAD_ERROR', |
| 15 | + DATA_PARSE: 'DATA_PARSE_ERROR', |
| 16 | + RENDER: 'RENDER_ERROR', |
| 17 | + FILTER: 'FILTER_ERROR', |
| 18 | + EXPORT: 'EXPORT_ERROR', |
| 19 | + UNKNOWN: 'UNKNOWN_ERROR' |
| 20 | +}; |
| 21 | + |
| 22 | +/** |
| 23 | + * Global error handler |
| 24 | + */ |
| 25 | +class ErrorHandler { |
| 26 | + constructor() { |
| 27 | + this.errors = []; |
| 28 | + this.maxErrors = 50; // Keep last 50 errors |
| 29 | + this.setupGlobalHandlers(); |
| 30 | + } |
| 31 | + |
| 32 | + /** |
| 33 | + * Setup global error handlers |
| 34 | + */ |
| 35 | + setupGlobalHandlers() { |
| 36 | + // Handle unhandled promise rejections |
| 37 | + window.addEventListener('unhandledrejection', (event) => { |
| 38 | + logger.error('Unhandled promise rejection:', event.reason); |
| 39 | + this.handleError(event.reason, ERROR_TYPES.UNKNOWN, 'Unhandled Promise'); |
| 40 | + event.preventDefault(); |
| 41 | + }); |
| 42 | + |
| 43 | + // Handle global errors |
| 44 | + window.addEventListener('error', (event) => { |
| 45 | + logger.error('Global error:', event.error || event.message); |
| 46 | + this.handleError( |
| 47 | + event.error || new Error(event.message), |
| 48 | + ERROR_TYPES.UNKNOWN, |
| 49 | + 'Global Error' |
| 50 | + ); |
| 51 | + }); |
| 52 | + } |
| 53 | + |
| 54 | + /** |
| 55 | + * Handle and log error |
| 56 | + * @param {Error} error - The error object |
| 57 | + * @param {string} type - Error type from ERROR_TYPES |
| 58 | + * @param {string} context - Context where error occurred |
| 59 | + * @param {boolean} showUser - Whether to show notification to user |
| 60 | + */ |
| 61 | + handleError(error, type = ERROR_TYPES.UNKNOWN, context = '', showUser = true) { |
| 62 | + const errorInfo = { |
| 63 | + timestamp: new Date().toISOString(), |
| 64 | + type, |
| 65 | + context, |
| 66 | + message: error?.message || String(error), |
| 67 | + stack: error?.stack || null |
| 68 | + }; |
| 69 | + |
| 70 | + // Store error |
| 71 | + this.errors.push(errorInfo); |
| 72 | + if (this.errors.length > this.maxErrors) { |
| 73 | + this.errors.shift(); |
| 74 | + } |
| 75 | + |
| 76 | + // Log error |
| 77 | + logger.error(`[${type}] ${context}:`, error); |
| 78 | + |
| 79 | + // Show user notification if needed |
| 80 | + if (showUser) { |
| 81 | + this.showUserError(type, errorInfo.message); |
| 82 | + } |
| 83 | + |
| 84 | + return errorInfo; |
| 85 | + } |
| 86 | + |
| 87 | + /** |
| 88 | + * Show user-friendly error message |
| 89 | + */ |
| 90 | + showUserError(type, message) { |
| 91 | + const userMessages = { |
| 92 | + [ERROR_TYPES.NETWORK]: 'Network error. Please check your connection.', |
| 93 | + [ERROR_TYPES.DATA_LOAD]: 'Failed to load data. Please refresh the page.', |
| 94 | + [ERROR_TYPES.DATA_PARSE]: 'Error processing data. Some features may not work.', |
| 95 | + [ERROR_TYPES.RENDER]: 'Display error. Please try again.', |
| 96 | + [ERROR_TYPES.FILTER]: 'Filter error. Please adjust your filters.', |
| 97 | + [ERROR_TYPES.EXPORT]: 'Export failed. Please try again.', |
| 98 | + [ERROR_TYPES.UNKNOWN]: 'An error occurred. Please try again.' |
| 99 | + }; |
| 100 | + |
| 101 | + const userMessage = userMessages[type] || userMessages[ERROR_TYPES.UNKNOWN]; |
| 102 | + |
| 103 | + if (typeof showNotification === 'function') { |
| 104 | + showNotification(userMessage, 'error'); |
| 105 | + } else { |
| 106 | + // Fallback to alert if notification not available |
| 107 | + console.error(userMessage); |
| 108 | + } |
| 109 | + } |
| 110 | + |
| 111 | + /** |
| 112 | + * Wrap async function with error handling |
| 113 | + * @param {Function} fn - Async function to wrap |
| 114 | + * @param {string} type - Error type |
| 115 | + * @param {string} context - Context description |
| 116 | + * @returns {Function} Wrapped function |
| 117 | + */ |
| 118 | + wrapAsync(fn, type, context) { |
| 119 | + return async (...args) => { |
| 120 | + try { |
| 121 | + return await fn(...args); |
| 122 | + } catch (error) { |
| 123 | + this.handleError(error, type, context); |
| 124 | + throw error; // Re-throw after handling |
| 125 | + } |
| 126 | + }; |
| 127 | + } |
| 128 | + |
| 129 | + /** |
| 130 | + * Wrap synchronous function with error handling |
| 131 | + * @param {Function} fn - Function to wrap |
| 132 | + * @param {string} type - Error type |
| 133 | + * @param {string} context - Context description |
| 134 | + * @param {*} fallback - Fallback value to return on error |
| 135 | + * @returns {Function} Wrapped function |
| 136 | + */ |
| 137 | + wrap(fn, type, context, fallback = null) { |
| 138 | + return (...args) => { |
| 139 | + try { |
| 140 | + return fn(...args); |
| 141 | + } catch (error) { |
| 142 | + this.handleError(error, type, context); |
| 143 | + return fallback; |
| 144 | + } |
| 145 | + }; |
| 146 | + } |
| 147 | + |
| 148 | + /** |
| 149 | + * Get error history |
| 150 | + */ |
| 151 | + getErrors() { |
| 152 | + return [...this.errors]; |
| 153 | + } |
| 154 | + |
| 155 | + /** |
| 156 | + * Clear error history |
| 157 | + */ |
| 158 | + clearErrors() { |
| 159 | + this.errors = []; |
| 160 | + } |
| 161 | + |
| 162 | + /** |
| 163 | + * Get error statistics |
| 164 | + */ |
| 165 | + getStats() { |
| 166 | + const stats = {}; |
| 167 | + this.errors.forEach(error => { |
| 168 | + stats[error.type] = (stats[error.type] || 0) + 1; |
| 169 | + }); |
| 170 | + return { |
| 171 | + total: this.errors.length, |
| 172 | + byType: stats, |
| 173 | + recent: this.errors.slice(-10) |
| 174 | + }; |
| 175 | + } |
| 176 | +} |
| 177 | + |
| 178 | +// Create singleton instance |
| 179 | +export const errorHandler = new ErrorHandler(); |
| 180 | + |
| 181 | +// Export convenience functions |
| 182 | +export const handleError = (error, type, context, showUser) => |
| 183 | + errorHandler.handleError(error, type, context, showUser); |
| 184 | + |
| 185 | +export const wrapAsync = (fn, type, context) => |
| 186 | + errorHandler.wrapAsync(fn, type, context); |
| 187 | + |
| 188 | +export const wrap = (fn, type, context, fallback) => |
| 189 | + errorHandler.wrap(fn, type, context, fallback); |
| 190 | + |
| 191 | +// Expose globally for debugging |
| 192 | +if (typeof window !== 'undefined') { |
| 193 | + window.errorHandler = errorHandler; |
| 194 | +} |
| 195 | + |
| 196 | +export default errorHandler; |
0 commit comments