Master comprehensive error handling strategies for APIs. Learn HTTP status codes, error recovery patterns, and building resilient applications.
Error handling is crucial for building robust web applications that interact with APIs. Users expect applications to work smoothly even when things go wrong, and proper error handling is what makes the difference between a frustrating experience and a professional one.
HTTP status codes are three-digit numbers that indicate the result of an HTTP request. Understanding these codes is essential for proper error handling.
Status Code Categories:
1xx - Informational (Rarely used in APIs):
2xx - Success:
3xx - Redirection:
4xx - Client Errors:
5xx - Server Errors:
Basic status code handling:
1async function handleAPIResponse(response) {
2 if (response.ok) {
3 // Success (200-299)
4 return await response.json();
5 }
6
7 // Handle different error categories
8 if (response.status >= 400 && response.status < 500) {
9 // Client errors
10 const errorData = await response.json();
11 throw new Error('Client Error (' + response.status + '): ' + errorData.message);
12 }
13
14 if (response.status >= 500) {
15 // Server errors
16 throw new Error('Server Error (' + response.status + '): Please try again later');
17 }
18
19 throw new Error('Unexpected status: ' + response.status);
20}Different types of errors require different recovery strategies. Building resilient applications means implementing appropriate responses for each error scenario.
Network Error Handling:
1async function robustFetch(url, options = {}) {
2 try {
3 const response = await fetch(url, options);
4 return await handleResponse(response);
5 } catch (error) {
6 if (error.name === 'TypeError') {
7 // Network error (no internet, DNS failure, etc.)
8 throw new Error('Network error: Please check your connection');
9 }
10 if (error.name === 'AbortError') {
11 // Request was cancelled
12 throw new Error('Request was cancelled');
13 }
14 // Re-throw other errors
15 throw error;
16 }
17}Retry Logic with Exponential Backoff:
1async function fetchWithRetry(url, options = {}, maxRetries = 3) {
2 for (let attempt = 1; attempt <= maxRetries; attempt++) {
3 try {
4 const response = await fetch(url, options);
5
6 // Don't retry client errors (4xx)
7 if (response.status >= 400 && response.status < 500) {
8 throw new Error('Client error: ' + response.status);
9 }
10
11 if (!response.ok) {
12 throw new Error('HTTP ' + response.status);
13 }
14
15 return await response.json();
16 } catch (error) {
17 if (attempt === maxRetries) {
18 throw error;
19 }
20
21 // Exponential backoff: 1s, 2s, 4s
22 const delay = Math.pow(2, attempt - 1) * 1000;
23 await new Promise(resolve => setTimeout(resolve, delay));
24 }
25 }
26}Timeout Handling:
1async function fetchWithTimeout(url, options = {}, timeoutMs = 10000) {
2 const controller = new AbortController();
3 const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
4
5 try {
6 const response = await fetch(url, {
7 ...options,
8 signal: controller.signal
9 });
10
11 clearTimeout(timeoutId);
12 return await response.json();
13 } catch (error) {
14 clearTimeout(timeoutId);
15
16 if (error.name === 'AbortError') {
17 throw new Error('Request timeout after ' + timeoutMs + 'ms');
18 }
19
20 throw error;
21 }
22}User-Friendly Error Messages:
1function getUserFriendlyError(error, context = '') {
2 const messages = {
3 400: 'Please check your input and try again.',
4 401: 'Please log in to continue.',
5 403: 'You don\'t have permission to perform this action.',
6 404: context + ' not found. It may have been deleted or moved.',
7 422: 'Please check your input for errors.',
8 429: 'Too many requests. Please wait a moment and try again.',
9 500: 'Something went wrong on our end. Please try again later.',
10 502: 'Service temporarily unavailable. Please try again.',
11 503: 'Service temporarily unavailable. Please try again.',
12 504: 'Request timed out. Please try again.'
13 };
14
15 if (error.status && messages[error.status]) {
16 return messages[error.status];
17 }
18
19 if (error.message.includes('network')) {
20 return 'Please check your internet connection and try again.';
21 }
22
23 if (error.message.includes('timeout')) {
24 return 'Request timed out. Please try again.';
25 }
26
27 return 'An unexpected error occurred. Please try again.';
28}Resilient applications gracefully handle errors and provide good user experiences even when things go wrong.
Comprehensive Error Handler:
1class APIErrorHandler {
2 constructor(options = {}) {
3 this.showUserMessages = options.showUserMessages !== false;
4 this.logErrors = options.logErrors !== false;
5 this.retryAttempts = options.retryAttempts || 3;
6 }
7
8 async handleRequest(requestFn, context = '') {
9 try {
10 return await this.executeWithRetry(requestFn);
11 } catch (error) {
12 return this.handleError(error, context);
13 }
14 }
15
16 async executeWithRetry(requestFn) {
17 let lastError;
18
19 for (let attempt = 1; attempt <= this.retryAttempts; attempt++) {
20 try {
21 return await requestFn();
22 } catch (error) {
23 lastError = error;
24
25 // Don't retry client errors
26 if (error.status >= 400 && error.status < 500) {
27 throw error;
28 }
29
30 if (attempt < this.retryAttempts) {
31 await this.delay(Math.pow(2, attempt - 1) * 1000);
32 }
33 }
34 }
35
36 throw lastError;
37 }
38
39 handleError(error, context) {
40 if (this.logErrors) {
41 console.error('API Error:', {
42 message: error.message,
43 status: error.status,
44 context: context,
45 timestamp: new Date().toISOString()
46 });
47 }
48
49 const userMessage = getUserFriendlyError(error, context);
50
51 if (this.showUserMessages) {
52 this.showErrorToUser(userMessage);
53 }
54
55 return {
56 success: false,
57 error: error.message,
58 userMessage: userMessage
59 };
60 }
61
62 showErrorToUser(message) {
63 // Implement your preferred user notification method
64 console.warn('User Error:', message);
65 }
66
67 delay(ms) {
68 return new Promise(resolve => setTimeout(resolve, ms));
69 }
70}
71
72// Usage
73const errorHandler = new APIErrorHandler({
74 showUserMessages: true,
75 logErrors: true,
76 retryAttempts: 3
77});
78
79async function fetchUserData(userId) {
80 return errorHandler.handleRequest(
81 () => fetch('/api/users/' + userId).then(r => r.json()),
82 'User data'
83 );
84}Circuit Breaker Pattern:
1class CircuitBreaker {
2 constructor(threshold = 5, timeout = 60000) {
3 this.threshold = threshold;
4 this.timeout = timeout;
5 this.failureCount = 0;
6 this.lastFailureTime = null;
7 this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
8 }
9
10 async execute(operation) {
11 if (this.state === 'OPEN') {
12 if (Date.now() - this.lastFailureTime > this.timeout) {
13 this.state = 'HALF_OPEN';
14 } else {
15 throw new Error('Circuit breaker is OPEN');
16 }
17 }
18
19 try {
20 const result = await operation();
21 this.onSuccess();
22 return result;
23 } catch (error) {
24 this.onFailure();
25 throw error;
26 }
27 }
28
29 onSuccess() {
30 this.failureCount = 0;
31 this.state = 'CLOSED';
32 }
33
34 onFailure() {
35 this.failureCount++;
36 this.lastFailureTime = Date.now();
37
38 if (this.failureCount >= this.threshold) {
39 this.state = 'OPEN';
40 }
41 }
42}
43
44// Usage
45const circuitBreaker = new CircuitBreaker(5, 60000);
46
47async function reliableAPICall() {
48 return circuitBreaker.execute(
49 () => fetch('/api/data').then(r => r.json())
50 );
51}Your progress is saved in this browser, on this device only.