Hone logo
Hone
Problems

Robust API Calls with Retry Logic in Angular

Many applications rely on external APIs, which can be unreliable due to network issues, server overload, or temporary outages. Implementing retry logic ensures your application gracefully handles these failures and attempts to recover, providing a better user experience. This challenge asks you to create a reusable Angular service that handles API calls with configurable retry attempts and delays.

Problem Description

You need to build an RetryableHttpService in Angular that wraps Angular's HttpClient and adds retry functionality. This service should handle HTTP requests (GET, POST, PUT, DELETE, PATCH) and automatically retry failed requests a specified number of times with an exponential backoff delay.

Key Requirements:

  • Retry Attempts: The service should accept a retryAttempts parameter (integer) to configure the maximum number of retry attempts.
  • Exponential Backoff: The delay between retries should increase exponentially (e.g., 1s, 2s, 4s, 8s...).
  • Error Handling: The service should handle different HTTP status codes appropriately. Retries should only be attempted for specific error ranges (e.g., 5xx errors, and potentially some 4xx errors like 429 - Too Many Requests). Other errors (e.g., 400 Bad Request) should not be retried.
  • Reusability: The service should be designed to be reusable across different components.
  • Observable Handling: The service should return an Observable that emits the successful response or an error after all retries have been exhausted.
  • Cancellation: Consider how to handle request cancellation if the component using the service is destroyed before the retry completes. (While not strictly required for a basic solution, it's a good consideration for robustness).

Expected Behavior:

  1. When a request fails (e.g., due to a 500 Internal Server Error), the service should retry the request.
  2. The delay between retries should increase exponentially.
  3. After the specified number of retry attempts, if the request still fails, the service should emit an error.
  4. The service should not retry requests that result in non-retryable errors (e.g., 400 Bad Request).
  5. The service should handle different HTTP methods (GET, POST, PUT, DELETE, PATCH).

Edge Cases to Consider:

  • What should happen if the HttpClient throws an error before the request is even sent (e.g., invalid URL)?
  • How should the service handle network errors (e.g., no internet connection)?
  • How to prevent infinite retry loops if the API consistently fails.

Examples

Example 1:

Input: retryAttempts = 3, URL = "/api/data", HTTP Method = GET, API returns 500 on first attempt, 200 on the third attempt.
Output: The Observable emits the 200 response after the third retry.
Explanation: The service retries the request twice after the initial 500 error, with increasing delays.  On the third attempt, the API returns a successful 200 response.

Example 2:

Input: retryAttempts = 2, URL = "/api/data", HTTP Method = POST, API returns 400 on both attempts.
Output: The Observable emits the 400 error after the second retry.
Explanation: The service attempts to retry the request twice, but the API consistently returns a 400 error, which is not retried.

Example 3:

Input: retryAttempts = 1, URL = "/api/data", HTTP Method = GET, API returns 503 on first attempt, and then 200 on the second attempt.
Output: The Observable emits the 200 response after the first retry.
Explanation: The service retries once after the 503 error, and the API returns a successful 200 response on the second attempt.

Constraints

  • retryAttempts must be a non-negative integer.
  • The exponential backoff delay should not exceed 30 seconds.
  • The service should be implemented using Angular's dependency injection.
  • The service should use Angular's HttpClient for making API calls.
  • The service should be testable.

Notes

  • Consider using RxJS operators like retry, delay, and mergeMap to implement the retry logic.
  • Think about how to configure the initial delay and the backoff factor.
  • You can use a simple exponential backoff formula like delay * 2^retryAttempt.
  • Focus on creating a clean, reusable, and testable service.
  • Error handling is crucial. Clearly define which errors should trigger retries and which should not.
  • Consider using a custom error class to differentiate between retryable and non-retryable errors. This can improve testability and error handling in the consuming components.
Loading editor...
typescript