MSDN Documentation

Mastering API Error Handling

Effective error handling is crucial for building robust and reliable applications that interact with APIs. This tutorial will guide you through common API error scenarios and provide practical strategies for handling them gracefully.

Understanding API Errors

APIs can fail for a multitude of reasons. Understanding these common failure points is the first step towards effective error handling:

HTTP Status Codes: Your First Line of Defense

HTTP status codes are standardized responses from the server that indicate the outcome of a request. Learning to interpret these codes is fundamental:

Best Practice: Always Check the Status Code

Before attempting to parse response data, always verify that the HTTP status code indicates a successful operation (typically in the 2xx range).

Handling Common Error Scenarios

Network Timeouts and Connectivity

Network issues can be transient. Implementing retry mechanisms with exponential backoff can help recover from temporary network glitches.


async function fetchDataWithRetry(url, retries = 3, delay = 1000) {
    try {
        const response = await fetch(url, { timeout: 5000 }); // Example timeout
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        return await response.json();
    } catch (error) {
        console.error(`Attempt failed: ${error.message}`);
        if (retries > 0) {
            await new Promise(resolve => setTimeout(resolve, delay));
            return fetchDataWithRetry(url, retries - 1, delay * 2); // Exponential backoff
        } else {
            throw new Error('Failed to fetch data after multiple retries.');
        }
    }
}
            

Parsing API Error Responses

Many APIs provide detailed error information in the response body, often in JSON format. This information can include error codes, messages, and even field-specific validation errors.


async function processApiRequest(url) {
    try {
        const response = await fetch(url);

        if (!response.ok) {
            const errorData = await response.json();
            console.error('API Error:', errorData);

            if (response.status === 400) {
                console.error('Bad Request: Check your input parameters.');
                // Display specific validation errors to the user
                if (errorData.errors) {
                    errorData.errors.forEach(err => console.error(`- ${err.message}`));
                }
            } else if (response.status === 401) {
                console.error('Unauthorized: Please log in.');
                // Redirect to login page
            } else if (response.status === 404) {
                console.error('Not Found: The requested resource does not exist.');
            } else {
                console.error(`An unexpected API error occurred: ${response.status}`);
            }
            return null; // Indicate failure
        }

        const data = await response.json();
        return data; // Success
    } catch (error) {
        console.error('Network or unexpected error:', error);
        // Handle network errors or JSON parsing errors
        return null; // Indicate failure
    }
}
            

Graceful Degradation and User Feedback

When errors occur, it's vital to provide clear and helpful feedback to the user without crashing the application. Avoid showing raw error messages that may be technical jargon.

Important: Don't Expose Sensitive Information

Never expose sensitive server-side error details (like stack traces or internal database errors) directly to the client. Always sanitize error messages before displaying them to users.

Advanced Error Handling Patterns

Example: Custom Error Object


class ApiError extends Error {
    constructor(message, statusCode, errorDetails) {
        super(message);
        this.name = 'ApiError';
        this.statusCode = statusCode;
        this.errorDetails = errorDetails;
    }
}

async function callApi() {
    try {
        const response = await fetch('/api/resource');
        if (!response.ok) {
            const errorData = await response.json();
            throw new ApiError(`API request failed with status ${response.status}`, response.status, errorData);
        }
        return await response.json();
    } catch (error) {
        if (error instanceof ApiError) {
            console.error(`API Error: ${error.message} (Status: ${error.statusCode})`);
            console.error('Details:', error.errorDetails);
            // Handle specific API error
        } else {
            console.error('Unexpected error:', error);
            // Handle other types of errors
        }
    }
}
            

By implementing these strategies, you can build more resilient applications that can gracefully handle the inevitable errors that occur when interacting with external APIs.