Skip to content

Commit fff45a5

Browse files
committed
Add centralised error handling
1 parent 1b78a78 commit fff45a5

1 file changed

Lines changed: 196 additions & 0 deletions

File tree

src/js/error-handler.js

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
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

Comments
 (0)