0
# Error Handling
1
2
Comprehensive error handling components for graceful failure display, debugging support, and user-friendly error presentation in Jupyter widget environments.
3
4
## Capabilities
5
6
### Error Widget Creation
7
8
Factory functions for creating widget models and views that display error information.
9
10
```typescript { .api }
11
/**
12
* Create a widget model class for displaying error information
13
* @param error - Error object or value to display
14
* @param msg - Optional additional error message
15
* @returns Widget model class configured for error display
16
*/
17
function createErrorWidgetModel(
18
error: unknown,
19
msg?: string
20
): typeof WidgetModel;
21
22
/**
23
* Create a widget view class for displaying custom error information
24
* @param error - Error object or value to display
25
* @param msg - Optional additional error message
26
* @returns Widget view class configured for error display
27
*/
28
function createErrorWidgetView(
29
error?: unknown,
30
msg?: string
31
): typeof WidgetView;
32
```
33
34
**Usage Examples:**
35
36
```typescript
37
// Create error model for widget creation failure
38
const handleWidgetCreationError = (error: Error) => {
39
const ErrorModel = createErrorWidgetModel(
40
error,
41
'Failed to create widget'
42
);
43
44
const errorWidget = new ErrorModel({}, {
45
model_id: generateId(),
46
widget_manager: this.widgetManager
47
});
48
49
return errorWidget;
50
};
51
52
// Create error view for rendering failure
53
const handleRenderError = (error: Error) => {
54
const ErrorView = createErrorWidgetView(
55
error,
56
'Failed to render widget content'
57
);
58
59
const errorView = new ErrorView({
60
model: this.model
61
});
62
63
return errorView;
64
};
65
66
// Handle async errors
67
try {
68
const data = await fetchWidgetData();
69
this.model.set('data', data);
70
} catch (error) {
71
const ErrorModel = createErrorWidgetModel(error, 'Data loading failed');
72
const fallbackWidget = new ErrorModel({}, {
73
model_id: 'error-' + Date.now(),
74
widget_manager: this.widgetManager
75
});
76
this.showErrorWidget(fallbackWidget);
77
}
78
```
79
80
### ErrorWidgetView
81
82
Pre-built view component for displaying error information with interactive error details.
83
84
```typescript { .api }
85
/**
86
* View component for displaying error information with click-to-expand functionality
87
*/
88
class ErrorWidgetView extends DOMWidgetView {
89
/**
90
* Extract error message and stack trace from the model
91
* @returns Object containing optional message and stack trace
92
*/
93
generateErrorMessage(): { msg?: string; stack: string };
94
95
/**
96
* Render the error widget with SVG icon and interactive error display
97
*/
98
render(): void;
99
}
100
```
101
102
The ErrorWidgetView provides:
103
- **Visual Error Icon**: Displays a broken file SVG icon to indicate an error state
104
- **Click to Expand**: Users can click the icon to view detailed error information
105
- **Stack Trace Display**: Shows full error stack trace and message when expanded
106
- **Double-click to Collapse**: Users can double-click to hide error details
107
- **Console Logging**: Encourages users to check browser console for additional details
108
109
**Usage Examples:**
110
111
```typescript
112
// Custom error view with additional context
113
class CustomErrorView extends ErrorWidgetView {
114
generateErrorMessage() {
115
const baseError = super.generateErrorMessage();
116
return {
117
...baseError,
118
msg: `Widget Error in ${this.model.get('widget_type')}: ${baseError.msg}`
119
};
120
}
121
122
render() {
123
super.render();
124
125
// Add custom styling
126
this.el.classList.add('custom-error-widget');
127
128
// Add additional error context
129
const contextDiv = document.createElement('div');
130
contextDiv.innerHTML = `
131
<small>Widget ID: ${this.model.model_id}</small><br>
132
<small>Time: ${new Date().toLocaleString()}</small>
133
`;
134
this.el.appendChild(contextDiv);
135
}
136
}
137
138
// Use in widget manager
139
const createErrorView = (model: WidgetModel, error: Error) => {
140
const errorView = new CustomErrorView({
141
model: model
142
});
143
144
// Set error data on model
145
model.set('error', error);
146
model.set('msg', 'Custom widget failed to initialize');
147
148
return errorView;
149
};
150
```
151
152
## Error Handling Patterns
153
154
### Widget Creation Error Handling
155
156
```typescript
157
// Robust widget creation with error fallback
158
const createWidgetWithFallback = async (
159
options: IModelOptions,
160
widgetManager: IWidgetManager
161
): Promise<WidgetModel> => {
162
try {
163
return await widgetManager.new_model(options);
164
} catch (error) {
165
console.error('Widget creation failed:', error);
166
167
// Create error widget as fallback
168
const ErrorModel = createErrorWidgetModel(
169
error,
170
`Failed to create ${options.model_name}`
171
);
172
173
return new ErrorModel({
174
_model_name: 'ErrorWidgetModel',
175
_view_name: 'ErrorWidgetView',
176
original_model_name: options.model_name,
177
original_module: options.model_module,
178
error_type: 'creation_failed'
179
}, {
180
model_id: options.model_id || generateId(),
181
widget_manager: widgetManager
182
});
183
}
184
};
185
186
// Widget manager integration
187
class RobustWidgetManager implements IWidgetManager {
188
async new_model(options: IModelOptions, state?: JSONObject): Promise<WidgetModel> {
189
return createWidgetWithFallback(options, this);
190
}
191
192
async create_view<VT extends WidgetView>(
193
model: WidgetModel,
194
options?: unknown
195
): Promise<VT> {
196
try {
197
return await this.createViewInternal(model, options);
198
} catch (error) {
199
console.error('View creation failed:', error);
200
201
// Return error view instead
202
const ErrorView = createErrorWidgetView(
203
error,
204
`Failed to create view for ${model.get('_model_name')}`
205
);
206
207
return new ErrorView({
208
model: model,
209
options: options
210
}) as VT;
211
}
212
}
213
}
214
```
215
216
### Communication Error Handling
217
218
```typescript
219
// Robust communication with error recovery
220
class RobustWidgetModel extends WidgetModel {
221
send(content: JSONValue, callbacks?: ICallbacks, buffers?: ArrayBuffer[]): void {
222
const enhancedCallbacks: ICallbacks = {
223
...callbacks,
224
shell: {
225
...callbacks?.shell,
226
error: (msg) => {
227
this.handleCommError(msg);
228
callbacks?.shell?.error?.(msg);
229
}
230
},
231
iopub: {
232
...callbacks?.iopub,
233
error: (msg) => {
234
this.handleIOPubError(msg);
235
callbacks?.iopub?.error?.(msg);
236
}
237
}
238
};
239
240
try {
241
super.send(content, enhancedCallbacks, buffers);
242
} catch (error) {
243
this.handleSendError(error);
244
}
245
}
246
247
private handleCommError(msg: any): void {
248
console.error('Comm error:', msg.content);
249
250
// Create error state
251
this.set('_error_state', {
252
type: 'comm_error',
253
message: msg.content.ename || 'Communication error',
254
details: msg.content.evalue || 'Unknown error',
255
timestamp: Date.now()
256
});
257
258
this.save_changes();
259
}
260
261
private handleIOPubError(msg: any): void {
262
console.error('IOPub error:', msg.content);
263
264
// Show error in UI if view exists
265
if (this.views) {
266
Object.values(this.views).forEach(async (viewPromise) => {
267
try {
268
const view = await viewPromise;
269
this.showErrorInView(view, msg.content);
270
} catch (viewError) {
271
console.error('Failed to show error in view:', viewError);
272
}
273
});
274
}
275
}
276
277
private handleSendError(error: any): void {
278
console.error('Send error:', error);
279
280
// Attempt to show error through error widget
281
const ErrorModel = createErrorWidgetModel(
282
error,
283
'Communication failed'
284
);
285
286
// Try to replace current widget with error widget
287
this.close();
288
// Implementation would replace widget in UI
289
}
290
291
private showErrorInView(view: WidgetView, errorContent: any): void {
292
// Add error overlay to existing view
293
const errorOverlay = document.createElement('div');
294
errorOverlay.className = 'widget-error-overlay';
295
errorOverlay.innerHTML = `
296
<div class="error-message">
297
<strong>Widget Error:</strong> ${errorContent.ename || 'Unknown error'}
298
<br>
299
<small>${errorContent.evalue || 'Check console for details'}</small>
300
</div>
301
`;
302
303
view.el.appendChild(errorOverlay);
304
}
305
}
306
```
307
308
### View Rendering Error Handling
309
310
```typescript
311
// Safe view rendering with error boundaries
312
class SafeWidgetView extends DOMWidgetView {
313
render(): void {
314
try {
315
this.renderContent();
316
} catch (error) {
317
this.renderError(error);
318
}
319
}
320
321
protected renderContent(): void {
322
// Override in subclasses
323
throw new Error('renderContent must be implemented');
324
}
325
326
protected renderError(error: Error): void {
327
console.error('View rendering failed:', error);
328
329
// Clear any partial content
330
this.el.innerHTML = '';
331
332
// Show error widget content
333
const errorView = new ErrorWidgetView({
334
model: this.model
335
});
336
337
// Set error data on model temporarily
338
const originalError = this.model.get('error');
339
const originalMsg = this.model.get('msg');
340
341
this.model.set({
342
error: error,
343
msg: 'Widget rendering failed'
344
});
345
346
errorView.render();
347
this.el.appendChild(errorView.el);
348
349
// Restore original values
350
this.model.set({
351
error: originalError,
352
msg: originalMsg
353
});
354
}
355
356
update(options?: any): void {
357
try {
358
this.updateContent(options);
359
} catch (error) {
360
console.error('View update failed:', error);
361
this.renderError(error);
362
}
363
}
364
365
protected updateContent(options?: any): void {
366
// Override in subclasses
367
}
368
}
369
370
// Usage in custom widgets
371
class ChartWidgetView extends SafeWidgetView {
372
protected renderContent(): void {
373
const data = this.model.get('data');
374
const chartType = this.model.get('chart_type');
375
376
if (!data || !chartType) {
377
throw new Error('Missing required data or chart type');
378
}
379
380
// Render chart content
381
this.renderChart(data, chartType);
382
}
383
384
protected updateContent(options?: any): void {
385
if (this.model.hasChanged('data') || this.model.hasChanged('chart_type')) {
386
this.renderContent();
387
}
388
}
389
}
390
```
391
392
### Async Error Handling
393
394
```typescript
395
// Handle async operations in widgets
396
class AsyncWidgetModel extends WidgetModel {
397
private async handleAsyncOperation<T>(
398
operation: () => Promise<T>,
399
errorContext: string
400
): Promise<T | null> {
401
try {
402
this.set('loading', true);
403
this.save_changes();
404
405
const result = await operation();
406
407
this.set({
408
loading: false,
409
error: null
410
});
411
this.save_changes();
412
413
return result;
414
} catch (error) {
415
console.error(`${errorContext} failed:`, error);
416
417
this.set({
418
loading: false,
419
error: {
420
context: errorContext,
421
message: error instanceof Error ? error.message : String(error),
422
stack: error instanceof Error ? error.stack : undefined,
423
timestamp: Date.now()
424
}
425
});
426
this.save_changes();
427
428
return null;
429
}
430
}
431
432
async loadData(): Promise<void> {
433
await this.handleAsyncOperation(
434
async () => {
435
const response = await fetch(this.get('data_url'));
436
if (!response.ok) {
437
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
438
}
439
const data = await response.json();
440
this.set('data', data);
441
return data;
442
},
443
'Data loading'
444
);
445
}
446
447
async saveChangesToServer(): Promise<void> {
448
await this.handleAsyncOperation(
449
async () => {
450
const response = await fetch(this.get('save_url'), {
451
method: 'POST',
452
headers: { 'Content-Type': 'application/json' },
453
body: JSON.stringify(this.get_state())
454
});
455
if (!response.ok) {
456
throw new Error(`Save failed: ${response.statusText}`);
457
}
458
return response.json();
459
},
460
'Saving changes'
461
);
462
}
463
}
464
465
// View that responds to error states
466
class AsyncWidgetView extends DOMWidgetView {
467
initialize(parameters: WidgetView.IInitializeParameters): void {
468
super.initialize(parameters);
469
470
this.listenTo(this.model, 'change:error', this.handleErrorChange);
471
this.listenTo(this.model, 'change:loading', this.handleLoadingChange);
472
}
473
474
private handleErrorChange(): void {
475
const error = this.model.get('error');
476
477
if (error) {
478
this.showError(error);
479
} else {
480
this.hideError();
481
}
482
}
483
484
private handleLoadingChange(): void {
485
const loading = this.model.get('loading');
486
487
if (loading) {
488
this.showLoadingIndicator();
489
} else {
490
this.hideLoadingIndicator();
491
}
492
}
493
494
private showError(error: any): void {
495
// Remove any existing error display
496
this.hideError();
497
498
const errorDiv = document.createElement('div');
499
errorDiv.className = 'widget-error-display';
500
errorDiv.innerHTML = `
501
<div class="error-header">
502
<strong>Error in ${error.context || 'Widget'}:</strong>
503
</div>
504
<div class="error-message">${error.message}</div>
505
<div class="error-time">
506
<small>Occurred at: ${new Date(error.timestamp).toLocaleString()}</small>
507
</div>
508
${error.stack ? `<details><summary>Stack Trace</summary><pre>${error.stack}</pre></details>` : ''}
509
`;
510
511
this.el.appendChild(errorDiv);
512
}
513
514
private hideError(): void {
515
const errorDisplay = this.el.querySelector('.widget-error-display');
516
if (errorDisplay) {
517
errorDisplay.remove();
518
}
519
}
520
521
private showLoadingIndicator(): void {
522
const loader = document.createElement('div');
523
loader.className = 'widget-loading';
524
loader.innerHTML = '<div class="loading-spinner"></div><span>Loading...</span>';
525
this.el.appendChild(loader);
526
}
527
528
private hideLoadingIndicator(): void {
529
const loader = this.el.querySelector('.widget-loading');
530
if (loader) {
531
loader.remove();
532
}
533
}
534
}
535
```
536
537
## Error Recovery Strategies
538
539
### Graceful Degradation
540
541
```typescript
542
// Widget that degrades gracefully when features are unavailable
543
class FeatureAwareWidget extends DOMWidgetView {
544
render(): void {
545
if (this.supportsAdvancedFeatures()) {
546
this.renderAdvanced();
547
} else if (this.supportsBasicFeatures()) {
548
this.renderBasic();
549
} else {
550
this.renderFallback();
551
}
552
}
553
554
private supportsAdvancedFeatures(): boolean {
555
return 'WebGL2RenderingContext' in window &&
556
'OffscreenCanvas' in window;
557
}
558
559
private supportsBasicFeatures(): boolean {
560
return 'Canvas' in window &&
561
'WebGLRenderingContext' in window;
562
}
563
564
private renderAdvanced(): void {
565
// High-performance WebGL2 rendering
566
}
567
568
private renderBasic(): void {
569
// Basic WebGL or canvas rendering
570
}
571
572
private renderFallback(): void {
573
// Simple HTML/CSS fallback
574
const message = document.createElement('div');
575
message.innerHTML = `
576
<div class="feature-warning">
577
<p>This browser doesn't support advanced rendering features.</p>
578
<p>Showing simplified version.</p>
579
</div>
580
`;
581
this.el.appendChild(message);
582
}
583
}
584
```
585
586
### Error Reporting
587
588
```typescript
589
// Error reporting and telemetry
590
class ErrorReporter {
591
static reportError(error: Error, context: string, widget?: WidgetModel): void {
592
const errorReport = {
593
message: error.message,
594
stack: error.stack,
595
context: context,
596
timestamp: Date.now(),
597
userAgent: navigator.userAgent,
598
widgetInfo: widget ? {
599
modelName: widget.get('_model_name'),
600
modelModule: widget.get('_model_module'),
601
modelId: widget.model_id
602
} : undefined
603
};
604
605
// Log to console
606
console.error('Widget Error Report:', errorReport);
607
608
// Send to error tracking service (if configured)
609
this.sendToErrorService(errorReport);
610
}
611
612
private static sendToErrorService(report: any): void {
613
// Implementation would send to error tracking service
614
// like Sentry, LogRocket, etc.
615
}
616
}
617
618
// Usage in widgets
619
class ReportingWidgetModel extends WidgetModel {
620
initialize(attributes: any, options: any): void {
621
try {
622
super.initialize(attributes, options);
623
} catch (error) {
624
ErrorReporter.reportError(
625
error instanceof Error ? error : new Error(String(error)),
626
'Widget initialization',
627
this
628
);
629
throw error;
630
}
631
}
632
}
633
```