0
# Error Handling and Retry Logic
1
2
Enhanced error objects with request/response context, automatic retry for specific status codes, configurable retry strategies, and comprehensive error information for debugging and monitoring.
3
4
## Capabilities
5
6
### FetchError Class
7
8
Enhanced error class that provides comprehensive information about fetch failures including request details, response data, and contextual information.
9
10
```typescript { .api }
11
/**
12
* Enhanced error class for fetch failures
13
* Provides comprehensive context about the failed request
14
*/
15
class FetchError<T = any> extends Error implements IFetchError<T> {
16
name: "FetchError";
17
request?: FetchRequest;
18
options?: FetchOptions;
19
response?: FetchResponse<T>;
20
data?: T;
21
status?: number;
22
statusText?: string;
23
statusCode?: number; // Alias for status
24
statusMessage?: string; // Alias for statusText
25
cause?: unknown;
26
}
27
28
interface IFetchError<T = any> extends Error {
29
request?: FetchRequest;
30
options?: FetchOptions;
31
response?: FetchResponse<T>;
32
data?: T;
33
status?: number;
34
statusText?: string;
35
statusCode?: number;
36
statusMessage?: string;
37
}
38
```
39
40
**Usage Examples:**
41
42
```typescript
43
import { ofetch, FetchError } from "ofetch";
44
45
try {
46
const data = await ofetch("https://api.example.com/users/999");
47
} catch (error) {
48
if (error instanceof FetchError) {
49
console.log("Request:", error.request);
50
console.log("Status:", error.status); // 404
51
console.log("Status Text:", error.statusText); // "Not Found"
52
console.log("Response Data:", error.data); // Parsed error response
53
console.log("Original Options:", error.options);
54
55
// Handle specific error types
56
if (error.status === 404) {
57
console.log("Resource not found");
58
} else if (error.status >= 500) {
59
console.log("Server error");
60
}
61
}
62
}
63
64
// Access error data for API error messages
65
try {
66
await ofetch("/api/users", { method: "POST", body: {} });
67
} catch (error) {
68
if (error instanceof FetchError && error.data) {
69
console.log("API Error:", error.data.message);
70
console.log("Validation Errors:", error.data.errors);
71
}
72
}
73
```
74
75
### Error Creation Factory
76
77
Factory function for creating FetchError instances with proper context population.
78
79
```typescript { .api }
80
/**
81
* Factory function to create FetchError instances
82
* @param ctx - Fetch context containing request, response, and error details
83
* @returns Enhanced FetchError with populated context
84
*/
85
function createFetchError<T = any>(ctx: FetchContext<T>): IFetchError<T>;
86
```
87
88
### Automatic Retry Logic
89
90
ofetch automatically retries requests for specific HTTP status codes with configurable retry counts and delays.
91
92
```typescript { .api }
93
interface FetchOptions {
94
/** Number of retry attempts, or false to disable retry */
95
retry?: number | false;
96
/** Delay between retries in milliseconds, or function for custom delay calculation */
97
retryDelay?: number | ((context: FetchContext<T, R>) => number);
98
/** HTTP status codes that trigger retries (default: [408, 409, 425, 429, 500, 502, 503, 504]) */
99
retryStatusCodes?: number[];
100
}
101
```
102
103
**Default Retry Status Codes:**
104
- `408` - Request Timeout
105
- `409` - Conflict
106
- `425` - Too Early (Experimental)
107
- `429` - Too Many Requests
108
- `500` - Internal Server Error
109
- `502` - Bad Gateway
110
- `503` - Service Unavailable
111
- `504` - Gateway Timeout
112
113
**Usage Examples:**
114
115
```typescript
116
import { ofetch } from "ofetch";
117
118
// Default retry behavior (1 retry for GET, 0 for POST/PUT/PATCH/DELETE)
119
const data = await ofetch("https://unreliable-api.com/data");
120
121
// Custom retry configuration
122
const data = await ofetch("https://api.example.com/data", {
123
retry: 3,
124
retryDelay: 1000, // 1 second between retries
125
retryStatusCodes: [408, 429, 500, 502, 503] // Custom retry codes
126
});
127
128
// Exponential backoff retry delay
129
const data = await ofetch("https://api.example.com/data", {
130
retry: 5,
131
retryDelay: (context) => {
132
const attempt = (context.options.retry || 0) - ((context.options as any)._retryCount || 0);
133
return Math.min(1000 * Math.pow(2, attempt), 30000); // Max 30 seconds
134
}
135
});
136
137
// Disable retry for specific request
138
const data = await ofetch("https://api.example.com/data", {
139
retry: false
140
});
141
142
// Force retry for POST requests (normally no retry)
143
const result = await ofetch("https://api.example.com/submit", {
144
method: "POST",
145
body: { data: "value" },
146
retry: 2 // Override default no-retry for POST
147
});
148
```
149
150
### Error Response Handling
151
152
Control whether ofetch throws errors for HTTP error status codes.
153
154
```typescript { .api }
155
interface FetchOptions {
156
/** Skip throwing errors for 4xx/5xx status codes */
157
ignoreResponseError?: boolean;
158
}
159
```
160
161
**Usage Examples:**
162
163
```typescript
164
import { ofetch } from "ofetch";
165
166
// Handle errors manually without throwing
167
const response = await ofetch.raw("https://api.example.com/might-fail", {
168
ignoreResponseError: true
169
});
170
171
if (response.status >= 400) {
172
console.log("Error:", response.status, response._data);
173
} else {
174
console.log("Success:", response._data);
175
}
176
177
// Conditional error handling
178
const data = await ofetch("https://api.example.com/users/999", {
179
ignoreResponseError: true
180
});
181
182
// For regular ofetch calls with ignoreResponseError, check manually
183
try {
184
const data = await ofetch("https://api.example.com/data", {
185
ignoreResponseError: true
186
});
187
// No error thrown, but data might be error response
188
if (data && data.error) {
189
console.log("API returned error:", data.error);
190
}
191
} catch (error) {
192
// Network or other non-HTTP errors still throw
193
console.log("Network error:", error.message);
194
}
195
```
196
197
### Error Context and Debugging
198
199
Comprehensive error context for debugging and monitoring.
200
201
**Usage Examples:**
202
203
```typescript
204
import { ofetch, FetchError } from "ofetch";
205
206
// Comprehensive error logging
207
try {
208
const data = await ofetch("https://api.example.com/data", {
209
method: "POST",
210
body: { invalid: "data" },
211
headers: { "Content-Type": "application/json" }
212
});
213
} catch (error) {
214
if (error instanceof FetchError) {
215
// Log full error context
216
console.error("Fetch Error Details:", {
217
message: error.message,
218
url: error.request,
219
method: error.options?.method,
220
status: error.status,
221
statusText: error.statusText,
222
requestHeaders: error.options?.headers,
223
responseHeaders: error.response?.headers,
224
requestBody: error.options?.body,
225
responseBody: error.data,
226
timestamp: new Date().toISOString()
227
});
228
}
229
}
230
231
// Error categorization for monitoring
232
function categorizeError(error: FetchError) {
233
if (!error.status) return "network_error";
234
if (error.status >= 400 && error.status < 500) return "client_error";
235
if (error.status >= 500) return "server_error";
236
return "unknown_error";
237
}
238
239
try {
240
await ofetch("https://api.example.com/data");
241
} catch (error) {
242
if (error instanceof FetchError) {
243
const category = categorizeError(error);
244
metrics.increment(`http_error.${category}`, {
245
status: error.status,
246
endpoint: error.request
247
});
248
}
249
}
250
```
251
252
### Timeout Handling
253
254
Configure request timeouts with automatic AbortController integration.
255
256
```typescript { .api }
257
interface FetchOptions {
258
/** Timeout in milliseconds */
259
timeout?: number;
260
}
261
```
262
263
**Usage Examples:**
264
265
```typescript
266
import { ofetch } from "ofetch";
267
268
// Request timeout
269
try {
270
const data = await ofetch("https://slow-api.com/data", {
271
timeout: 5000 // 5 second timeout
272
});
273
} catch (error) {
274
if (error.name === "TimeoutError") {
275
console.log("Request timed out after 5 seconds");
276
}
277
}
278
279
// Different timeouts for different operations
280
const api = ofetch.create({
281
baseURL: "https://api.example.com",
282
timeout: 10000 // Default 10 second timeout
283
});
284
285
// Override timeout for specific requests
286
const quickData = await api("/quick-endpoint", { timeout: 2000 });
287
const slowData = await api("/slow-endpoint", { timeout: 30000 });
288
```
289
290
### Error Handling Patterns
291
292
Common patterns for handling different types of errors in applications.
293
294
**Usage Examples:**
295
296
```typescript
297
import { ofetch, FetchError } from "ofetch";
298
299
// Wrapper function with comprehensive error handling
300
async function apiRequest<T>(url: string, options?: any): Promise<T> {
301
try {
302
return await ofetch<T>(url, options);
303
} catch (error) {
304
if (error instanceof FetchError) {
305
// Handle specific HTTP errors
306
switch (error.status) {
307
case 401:
308
throw new Error("Authentication required");
309
case 403:
310
throw new Error("Access denied");
311
case 404:
312
throw new Error("Resource not found");
313
case 429:
314
throw new Error("Rate limit exceeded");
315
case 500:
316
throw new Error("Server error - please try again later");
317
default:
318
throw new Error(`Request failed: ${error.message}`);
319
}
320
}
321
322
// Handle network errors
323
if (error.name === "TimeoutError") {
324
throw new Error("Request timeout - please check your connection");
325
}
326
327
throw error; // Re-throw unknown errors
328
}
329
}
330
331
// Retry with custom error handling
332
async function retryableRequest<T>(url: string, maxRetries = 3): Promise<T> {
333
let lastError: Error;
334
335
for (let attempt = 1; attempt <= maxRetries; attempt++) {
336
try {
337
return await ofetch<T>(url, {
338
timeout: 5000 * attempt, // Increasing timeout
339
retry: false // Handle retry manually
340
});
341
} catch (error) {
342
lastError = error;
343
344
if (error instanceof FetchError) {
345
// Don't retry client errors (4xx)
346
if (error.status && error.status >= 400 && error.status < 500) {
347
throw error;
348
}
349
}
350
351
if (attempt < maxRetries) {
352
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
353
}
354
}
355
}
356
357
throw lastError!;
358
}
359
360
// Global error handler with logging
361
const api = ofetch.create({
362
baseURL: "https://api.example.com",
363
onResponseError({ request, response, error }) {
364
// Log all API errors
365
logger.error("API Error", {
366
url: request,
367
status: response.status,
368
data: response._data,
369
timestamp: new Date().toISOString()
370
});
371
}
372
});
373
```