0
# Error Handling
1
2
Comprehensive error handling with detailed HTTP and timeout error classes containing request and response information. Ky automatically throws errors for non-2xx responses and provides detailed error objects.
3
4
## Capabilities
5
6
### HTTPError Class
7
8
Thrown for HTTP responses with non-2xx status codes (when `throwHttpErrors` is true).
9
10
```typescript { .api }
11
/**
12
* HTTP Error class for non-2xx responses
13
* Contains detailed information about the failed request and response
14
*/
15
class HTTPError<T = unknown> extends Error {
16
/** The HTTP response that caused the error */
17
response: KyResponse<T>;
18
/** The original request */
19
request: KyRequest;
20
/** The normalized options used for the request */
21
options: NormalizedOptions;
22
/** Error name */
23
name: "HTTPError";
24
/** Descriptive error message with status and URL */
25
message: string;
26
}
27
```
28
29
**Usage Examples:**
30
31
```typescript
32
import ky, { HTTPError } from "ky";
33
34
try {
35
const data = await ky.get("https://api.example.com/not-found").json();
36
} catch (error) {
37
if (error instanceof HTTPError) {
38
console.log("HTTP Error Details:");
39
console.log("Status:", error.response.status);
40
console.log("Status Text:", error.response.statusText);
41
console.log("URL:", error.request.url);
42
console.log("Method:", error.request.method);
43
console.log("Message:", error.message);
44
45
// Access response body for more details
46
const errorBody = await error.response.text();
47
console.log("Response Body:", errorBody);
48
49
// Check specific status codes
50
if (error.response.status === 404) {
51
console.log("Resource not found");
52
} else if (error.response.status === 401) {
53
console.log("Authentication required");
54
} else if (error.response.status >= 500) {
55
console.log("Server error");
56
}
57
}
58
}
59
60
// Handle different error types
61
const handleApiCall = async () => {
62
try {
63
return await ky.post("https://api.example.com/data", {
64
json: { value: "test" }
65
}).json();
66
} catch (error) {
67
if (error instanceof HTTPError) {
68
// HTTP errors (4xx, 5xx responses)
69
switch (error.response.status) {
70
case 400:
71
throw new Error("Invalid request data");
72
case 401:
73
throw new Error("Please log in to continue");
74
case 403:
75
throw new Error("You don't have permission for this action");
76
case 404:
77
throw new Error("The requested resource was not found");
78
case 429:
79
throw new Error("Rate limit exceeded. Please try again later");
80
case 500:
81
throw new Error("Server error. Please try again later");
82
default:
83
throw new Error(`Request failed with status ${error.response.status}`);
84
}
85
} else {
86
// Network errors, timeouts, etc.
87
throw new Error("Network error. Please check your connection");
88
}
89
}
90
};
91
```
92
93
### TimeoutError Class
94
95
Thrown when requests exceed the configured timeout duration.
96
97
```typescript { .api }
98
/**
99
* Timeout Error class for requests that exceed timeout duration
100
* Contains information about the request that timed out
101
*/
102
class TimeoutError extends Error {
103
/** The original request that timed out */
104
request: KyRequest;
105
/** Error name */
106
name: "TimeoutError";
107
/** Descriptive error message with method and URL */
108
message: string;
109
}
110
```
111
112
**Usage Examples:**
113
114
```typescript
115
import ky, { TimeoutError } from "ky";
116
117
try {
118
const data = await ky.get("https://api.example.com/slow-endpoint", {
119
timeout: 5000 // 5 second timeout
120
}).json();
121
} catch (error) {
122
if (error instanceof TimeoutError) {
123
console.log("Request timed out:");
124
console.log("URL:", error.request.url);
125
console.log("Method:", error.request.method);
126
console.log("Message:", error.message);
127
128
// Handle timeout specifically
129
throw new Error("The request took too long. Please try again");
130
}
131
}
132
133
// Timeout handling with retry logic
134
const fetchWithTimeoutHandling = async (url: string, maxAttempts = 3) => {
135
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
136
try {
137
return await ky.get(url, {
138
timeout: 10000 // 10 seconds
139
}).json();
140
} catch (error) {
141
if (error instanceof TimeoutError) {
142
console.log(`Attempt ${attempt} timed out`);
143
144
if (attempt === maxAttempts) {
145
throw new Error("Request failed: Multiple timeout attempts");
146
}
147
148
// Wait before retrying
149
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
150
continue;
151
}
152
153
// Re-throw non-timeout errors immediately
154
throw error;
155
}
156
}
157
};
158
```
159
160
### Error Handling Patterns
161
162
Common patterns for handling different types of errors in applications.
163
164
**Usage Examples:**
165
166
```typescript
167
import ky, { HTTPError, TimeoutError } from "ky";
168
169
// Comprehensive error handling function
170
const safeApiCall = async <T>(
171
url: string,
172
options?: any
173
): Promise<{ data?: T; error?: string }> => {
174
try {
175
const data = await ky(url, options).json<T>();
176
return { data };
177
} catch (error) {
178
if (error instanceof HTTPError) {
179
// Handle HTTP errors
180
const status = error.response.status;
181
182
if (status >= 400 && status < 500) {
183
// Client errors
184
return { error: `Client error: ${status}` };
185
} else if (status >= 500) {
186
// Server errors
187
return { error: "Server error. Please try again later" };
188
}
189
} else if (error instanceof TimeoutError) {
190
// Handle timeouts
191
return { error: "Request timeout. Please try again" };
192
} else {
193
// Handle other errors (network, etc.)
194
return { error: "Network error. Please check your connection" };
195
}
196
197
return { error: "An unexpected error occurred" };
198
}
199
};
200
201
// Usage of safe API call
202
const loadUserData = async (userId: string) => {
203
const { data, error } = await safeApiCall<User>(`/api/users/${userId}`);
204
205
if (error) {
206
showErrorMessage(error);
207
return null;
208
}
209
210
return data;
211
};
212
213
// Error boundary for React-like patterns
214
const withErrorHandling = <T extends any[], R>(
215
fn: (...args: T) => Promise<R>
216
) => {
217
return async (...args: T): Promise<R | null> => {
218
try {
219
return await fn(...args);
220
} catch (error) {
221
if (error instanceof HTTPError) {
222
// Log HTTP errors with context
223
console.error("HTTP Error:", {
224
url: error.request.url,
225
method: error.request.method,
226
status: error.response.status,
227
statusText: error.response.statusText
228
});
229
230
// Get response body for debugging
231
try {
232
const body = await error.response.clone().text();
233
console.error("Response body:", body);
234
} catch {
235
// Ignore if body can't be read
236
}
237
} else if (error instanceof TimeoutError) {
238
console.error("Timeout Error:", {
239
url: error.request.url,
240
method: error.request.method
241
});
242
} else {
243
console.error("Unexpected error:", error);
244
}
245
246
return null;
247
}
248
};
249
};
250
251
// Wrapped API functions
252
const getUser = withErrorHandling(async (id: string) => {
253
return ky.get(`/api/users/${id}`).json<User>();
254
});
255
256
const createUser = withErrorHandling(async (userData: CreateUserData) => {
257
return ky.post("/api/users", { json: userData }).json<User>();
258
});
259
```
260
261
### Disabling Automatic Error Throwing
262
263
Configure ky to not throw errors for non-2xx responses.
264
265
**Usage Examples:**
266
267
```typescript
268
import ky from "ky";
269
270
// Disable error throwing globally
271
const tolerantClient = ky.create({
272
throwHttpErrors: false
273
});
274
275
// Handle responses manually
276
const response = await tolerantClient.get("https://api.example.com/data");
277
278
if (response.ok) {
279
const data = await response.json();
280
console.log("Success:", data);
281
} else {
282
console.log("Error:", response.status, response.statusText);
283
284
// Get error details from response
285
const errorText = await response.text();
286
console.log("Error details:", errorText);
287
}
288
289
// Per-request error handling
290
const checkResourceExists = async (url: string): Promise<boolean> => {
291
const response = await ky.get(url, {
292
throwHttpErrors: false
293
});
294
295
return response.ok;
296
};
297
298
// Manual error handling with detailed information
299
const manualErrorHandling = async () => {
300
const response = await ky.post("https://api.example.com/submit", {
301
json: { data: "test" },
302
throwHttpErrors: false
303
});
304
305
if (!response.ok) {
306
// Handle different status codes manually
307
switch (response.status) {
308
case 400:
309
const validationErrors = await response.json();
310
console.log("Validation errors:", validationErrors);
311
break;
312
case 401:
313
console.log("Authentication required");
314
// Redirect to login
315
break;
316
case 403:
317
console.log("Access forbidden");
318
break;
319
case 404:
320
console.log("Resource not found");
321
break;
322
case 429:
323
const retryAfter = response.headers.get("Retry-After");
324
console.log(`Rate limited. Retry after: ${retryAfter}`);
325
break;
326
case 500:
327
console.log("Internal server error");
328
break;
329
default:
330
console.log(`Unexpected error: ${response.status}`);
331
}
332
333
return null;
334
}
335
336
return response.json();
337
};
338
```
339
340
### Custom Error Enhancement
341
342
Use hooks to enhance errors with additional information.
343
344
**Usage Examples:**
345
346
```typescript
347
import ky, { HTTPError } from "ky";
348
349
// Client with enhanced error messages
350
const enhancedClient = ky.create({
351
hooks: {
352
beforeError: [
353
async (error) => {
354
// Add response body to error message
355
try {
356
const responseBody = await error.response.clone().text();
357
if (responseBody) {
358
error.message += `\nResponse: ${responseBody}`;
359
}
360
} catch {
361
// Ignore if response body can't be read
362
}
363
364
// Add request details
365
error.message += `\nRequest: ${error.request.method} ${error.request.url}`;
366
367
// Add headers for debugging
368
const headers = Object.fromEntries(error.request.headers);
369
error.message += `\nHeaders: ${JSON.stringify(headers, null, 2)}`;
370
371
return error;
372
}
373
]
374
}
375
});
376
377
// Custom error class
378
class ApiError extends Error {
379
constructor(
380
message: string,
381
public status: number,
382
public code?: string,
383
public details?: any
384
) {
385
super(message);
386
this.name = "ApiError";
387
}
388
}
389
390
// Client that transforms HTTPError to custom error
391
const customErrorClient = ky.create({
392
hooks: {
393
beforeError: [
394
async (error) => {
395
try {
396
const errorData = await error.response.clone().json();
397
398
// Transform to custom error if response has expected format
399
if (errorData.message || errorData.error) {
400
const customError = new ApiError(
401
errorData.message || errorData.error,
402
error.response.status,
403
errorData.code,
404
errorData.details
405
);
406
407
// Preserve original error properties
408
(customError as any).response = error.response;
409
(customError as any).request = error.request;
410
(customError as any).options = error.options;
411
412
return customError as any;
413
}
414
} catch {
415
// Fall back to original error if transformation fails
416
}
417
418
return error;
419
}
420
]
421
}
422
});
423
424
// Usage with custom error
425
try {
426
await customErrorClient.post("/api/data", {
427
json: { invalid: "data" }
428
}).json();
429
} catch (error) {
430
if (error instanceof ApiError) {
431
console.log("API Error:", error.message);
432
console.log("Status:", error.status);
433
console.log("Code:", error.code);
434
console.log("Details:", error.details);
435
} else if (error instanceof HTTPError) {
436
console.log("HTTP Error:", error.message);
437
}
438
}
439
```
440
441
## Types
442
443
```typescript { .api }
444
class HTTPError<T = unknown> extends Error {
445
response: KyResponse<T>;
446
request: KyRequest;
447
options: NormalizedOptions;
448
name: "HTTPError";
449
}
450
451
class TimeoutError extends Error {
452
request: KyRequest;
453
name: "TimeoutError";
454
}
455
456
interface KyResponse<T = unknown> extends Response {
457
json<J = T>(): Promise<J>;
458
}
459
460
interface KyRequest<T = unknown> extends Request {
461
json<J = T>(): Promise<J>;
462
}
463
464
interface NormalizedOptions extends RequestInit {
465
method: NonNullable<RequestInit['method']>;
466
credentials?: NonNullable<RequestInit['credentials']>;
467
retry: RetryOptions;
468
prefixUrl: string;
469
onDownloadProgress: Options['onDownloadProgress'];
470
onUploadProgress: Options['onUploadProgress'];
471
}
472
```