0
# Error Handling
1
2
Specialized error classes for different types of failures during HTTP operations, providing detailed error information for proper debugging and error recovery.
3
4
## Capabilities
5
6
### FetchError Class
7
8
Operational error class for network failures, timeouts, and other fetch-related issues.
9
10
```javascript { .api }
11
/**
12
* Error class for fetch operation failures
13
*/
14
class FetchError extends Error {
15
constructor(message: string, type: string, systemError?: Record<string, unknown>);
16
17
readonly name: 'FetchError';
18
readonly type: string;
19
readonly code?: string;
20
readonly errno?: string;
21
readonly erroredSysCall?: string;
22
}
23
```
24
25
**Common Error Types:**
26
27
- `'system'` - Network-level errors (DNS resolution, connection failures)
28
- `'max-redirect'` - Too many redirects encountered
29
- `'max-size'` - Response body exceeded size limit
30
- `'invalid-redirect'` - Invalid redirect URL
31
- `'no-redirect'` - Redirect encountered when redirect: 'error'
32
- `'unsupported-redirect'` - Cannot redirect with non-replayable body
33
34
**Usage Examples:**
35
36
```javascript
37
import fetch, { FetchError } from 'node-fetch';
38
39
try {
40
const response = await fetch('https://nonexistent-domain.example');
41
} catch (error) {
42
if (error instanceof FetchError) {
43
console.log('Fetch error type:', error.type);
44
console.log('Error message:', error.message);
45
46
switch (error.type) {
47
case 'system':
48
console.log('Network error:', error.code);
49
if (error.code === 'ENOTFOUND') {
50
console.log('Domain not found');
51
} else if (error.code === 'ECONNREFUSED') {
52
console.log('Connection refused');
53
}
54
break;
55
56
case 'max-redirect':
57
console.log('Too many redirects');
58
break;
59
60
case 'max-size':
61
console.log('Response too large');
62
break;
63
}
64
}
65
}
66
```
67
68
### AbortError Class
69
70
Error class specifically for cancelled/aborted requests using AbortSignal.
71
72
```javascript { .api }
73
/**
74
* Error class for aborted requests
75
*/
76
class AbortError extends Error {
77
constructor(message: string, type?: string);
78
79
readonly name: 'AbortError';
80
readonly type: string;
81
}
82
```
83
84
**Usage Examples:**
85
86
```javascript
87
import fetch, { AbortError } from 'node-fetch';
88
89
// Request cancellation with timeout
90
const controller = new AbortController();
91
const timeoutId = setTimeout(() => controller.abort(), 5000);
92
93
try {
94
const response = await fetch('https://api.example.com/slow-endpoint', {
95
signal: controller.signal
96
});
97
98
clearTimeout(timeoutId);
99
const data = await response.json();
100
101
} catch (error) {
102
if (error instanceof AbortError) {
103
console.log('Request was cancelled:', error.message);
104
console.log('Error type:', error.type); // 'aborted'
105
} else {
106
console.log('Other error:', error.message);
107
}
108
}
109
```
110
111
### System Error Details
112
113
When FetchError has type 'system', it includes Node.js system error details.
114
115
```javascript { .api }
116
interface SystemErrorDetails {
117
code?: string; // System error code (ENOTFOUND, ECONNREFUSED, etc.)
118
errno?: string; // System error number
119
erroredSysCall?: string; // System call that failed
120
}
121
```
122
123
**Usage Examples:**
124
125
```javascript
126
try {
127
await fetch('https://192.168.1.999:8080/api'); // Invalid IP
128
} catch (error) {
129
if (error instanceof FetchError && error.type === 'system') {
130
console.log('System error code:', error.code);
131
console.log('System call:', error.erroredSysCall);
132
console.log('Error number:', error.errno);
133
134
// Handle specific system errors
135
switch (error.code) {
136
case 'ENOTFOUND':
137
console.log('DNS lookup failed - domain not found');
138
break;
139
case 'ECONNREFUSED':
140
console.log('Connection refused - server not accepting connections');
141
break;
142
case 'ETIMEDOUT':
143
console.log('Connection timed out');
144
break;
145
case 'ECONNRESET':
146
console.log('Connection reset by peer');
147
break;
148
default:
149
console.log('Unknown system error:', error.code);
150
}
151
}
152
}
153
```
154
155
### Error Handling Patterns
156
157
Common patterns for handling different types of errors in fetch operations.
158
159
```javascript { .api }
160
interface ErrorHandlingPattern {
161
handleNetworkError(error: FetchError): void;
162
handleHttpError(response: Response): Promise<void>;
163
retryWithBackoff(url: string, options?: RequestInit, maxRetries?: number): Promise<Response>;
164
}
165
```
166
167
**Network Error Handling:**
168
169
```javascript
170
async function robustFetch(url, options = {}, retries = 3) {
171
for (let attempt = 1; attempt <= retries; attempt++) {
172
try {
173
return await fetch(url, options);
174
} catch (error) {
175
if (error instanceof FetchError) {
176
console.log(`Attempt ${attempt} failed:`, error.type);
177
178
// Don't retry certain error types
179
if (error.type === 'max-size' || error.type === 'invalid-redirect') {
180
throw error;
181
}
182
183
// Only retry system errors (network issues)
184
if (error.type !== 'system' || attempt === retries) {
185
throw error;
186
}
187
188
// Wait before retry with exponential backoff
189
const delay = Math.pow(2, attempt - 1) * 1000;
190
await new Promise(resolve => setTimeout(resolve, delay));
191
192
} else if (error instanceof AbortError) {
193
// Don't retry aborted requests
194
throw error;
195
} else {
196
throw error;
197
}
198
}
199
}
200
}
201
```
202
203
**HTTP Error Handling:**
204
205
```javascript
206
async function fetchWithErrorHandling(url, options = {}) {
207
let response;
208
209
try {
210
response = await fetch(url, options);
211
} catch (error) {
212
if (error instanceof FetchError) {
213
throw new Error(`Network error: ${error.message}`);
214
} else if (error instanceof AbortError) {
215
throw new Error('Request was cancelled');
216
} else {
217
throw error;
218
}
219
}
220
221
// Handle HTTP error status codes
222
if (!response.ok) {
223
let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
224
225
try {
226
// Try to get error details from response body
227
const contentType = response.headers.get('content-type');
228
229
if (contentType?.includes('application/json')) {
230
const errorBody = await response.json();
231
errorMessage += ` - ${errorBody.message || errorBody.error || 'Unknown error'}`;
232
} else if (contentType?.includes('text/')) {
233
const errorText = await response.text();
234
if (errorText.length < 200) { // Avoid logging huge HTML error pages
235
errorMessage += ` - ${errorText}`;
236
}
237
}
238
} catch (bodyError) {
239
// Ignore errors reading response body
240
}
241
242
throw new Error(errorMessage);
243
}
244
245
return response;
246
}
247
```
248
249
### Timeout Handling
250
251
Implementing request timeouts using AbortController and handling timeout errors.
252
253
```javascript { .api }
254
interface TimeoutConfig {
255
timeout: number;
256
signal?: AbortSignal;
257
}
258
```
259
260
**Usage Examples:**
261
262
```javascript
263
// Simple timeout wrapper
264
function fetchWithTimeout(url, options = {}, timeout = 5000) {
265
const controller = new AbortController();
266
const id = setTimeout(() => controller.abort(), timeout);
267
268
return fetch(url, {
269
...options,
270
signal: controller.signal
271
}).finally(() => clearTimeout(id));
272
}
273
274
// Advanced timeout with custom error
275
class TimeoutError extends Error {
276
constructor(timeout) {
277
super(`Request timed out after ${timeout}ms`);
278
this.name = 'TimeoutError';
279
this.timeout = timeout;
280
}
281
}
282
283
async function fetchWithCustomTimeout(url, options = {}, timeout = 10000) {
284
const controller = new AbortController();
285
const timeoutId = setTimeout(() => controller.abort(), timeout);
286
287
try {
288
const response = await fetch(url, {
289
...options,
290
signal: controller.signal
291
});
292
293
clearTimeout(timeoutId);
294
return response;
295
296
} catch (error) {
297
clearTimeout(timeoutId);
298
299
if (error instanceof AbortError) {
300
throw new TimeoutError(timeout);
301
}
302
throw error;
303
}
304
}
305
306
// Usage with error handling
307
try {
308
const response = await fetchWithCustomTimeout(
309
'https://api.example.com/slow-endpoint',
310
{ method: 'GET' },
311
3000
312
);
313
const data = await response.json();
314
} catch (error) {
315
if (error instanceof TimeoutError) {
316
console.log('Request timed out:', error.timeout);
317
} else if (error instanceof FetchError) {
318
console.log('Network error:', error.message);
319
} else {
320
console.log('Other error:', error.message);
321
}
322
}
323
```
324
325
### Redirect Error Handling
326
327
Special handling for redirect-related errors and manual redirect processing.
328
329
```javascript { .api }
330
interface RedirectErrorHandling {
331
handleRedirectError(error: FetchError): void;
332
followRedirectsManually(url: string, options?: RequestInit): Promise<Response>;
333
}
334
```
335
336
**Usage Examples:**
337
338
```javascript
339
// Handle redirect limits
340
try {
341
const response = await fetch('https://example.com/redirect-chain', {
342
follow: 3 // Limit to 3 redirects
343
});
344
} catch (error) {
345
if (error instanceof FetchError && error.type === 'max-redirect') {
346
console.log('Too many redirects encountered');
347
// Could implement manual redirect handling here
348
}
349
}
350
351
// Manual redirect handling
352
async function followRedirectsManually(url, options = {}, maxRedirects = 5) {
353
let currentUrl = url;
354
let redirectCount = 0;
355
356
while (redirectCount < maxRedirects) {
357
const response = await fetch(currentUrl, {
358
...options,
359
redirect: 'manual'
360
});
361
362
if (response.status >= 300 && response.status < 400) {
363
const location = response.headers.get('location');
364
if (!location) {
365
throw new Error('Redirect response missing Location header');
366
}
367
368
currentUrl = new URL(location, currentUrl).toString();
369
redirectCount++;
370
console.log(`Redirect ${redirectCount}: ${currentUrl}`);
371
372
} else {
373
return response;
374
}
375
}
376
377
throw new Error(`Too many redirects (${maxRedirects})`);
378
}
379
```
380
381
### Error Recovery Strategies
382
383
Comprehensive error recovery patterns for robust HTTP clients.
384
385
```javascript { .api }
386
interface ErrorRecoveryStrategy {
387
exponentialBackoff(attempt: number): number;
388
shouldRetry(error: Error): boolean;
389
circuitBreaker(url: string): boolean;
390
}
391
```
392
393
**Usage Examples:**
394
395
```javascript
396
// Circuit breaker pattern
397
class CircuitBreaker {
398
constructor(threshold = 5, timeout = 60000) {
399
this.failures = new Map();
400
this.threshold = threshold;
401
this.timeout = timeout;
402
}
403
404
canExecute(url) {
405
const failure = this.failures.get(url);
406
if (!failure) return true;
407
408
if (failure.count >= this.threshold) {
409
if (Date.now() - failure.lastFailure < this.timeout) {
410
return false; // Circuit open
411
} else {
412
// Try again after timeout
413
this.failures.delete(url);
414
return true;
415
}
416
}
417
return true;
418
}
419
420
recordSuccess(url) {
421
this.failures.delete(url);
422
}
423
424
recordFailure(url) {
425
const failure = this.failures.get(url) || { count: 0, lastFailure: 0 };
426
failure.count++;
427
failure.lastFailure = Date.now();
428
this.failures.set(url, failure);
429
}
430
}
431
432
// Comprehensive error handling with circuit breaker
433
const circuitBreaker = new CircuitBreaker();
434
435
async function resilientFetch(url, options = {}, maxRetries = 3) {
436
if (!circuitBreaker.canExecute(url)) {
437
throw new Error('Circuit breaker is open for this URL');
438
}
439
440
for (let attempt = 1; attempt <= maxRetries; attempt++) {
441
try {
442
const response = await fetchWithTimeout(url, options, 10000);
443
444
if (!response.ok) {
445
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
446
}
447
448
circuitBreaker.recordSuccess(url);
449
return response;
450
451
} catch (error) {
452
console.log(`Attempt ${attempt} failed:`, error.message);
453
454
// Don't retry certain errors
455
if (error instanceof AbortError ||
456
(error instanceof FetchError && error.type === 'max-size')) {
457
circuitBreaker.recordFailure(url);
458
throw error;
459
}
460
461
if (attempt === maxRetries) {
462
circuitBreaker.recordFailure(url);
463
throw error;
464
}
465
466
// Exponential backoff
467
const delay = Math.min(1000 * Math.pow(2, attempt - 1), 10000);
468
await new Promise(resolve => setTimeout(resolve, delay));
469
}
470
}
471
}
472
```