Fetch And Retry
PromisesAsync
Implement a retry function that automatically retries a failing async operation a specified number of times.
Function Signature:
function retry(asyncFn, retries = 3, delay = 0)
Examples
Example 1
Input
// Success on first try
retry(() => createMockApi(true, 50), 3, 100)
.then(result => console.log('Success:', result))
.catch(error => console.error('Error:', error));Output
Success: { statusCode: 200, message: 'Success!' }Explanation
The function succeeds on the first attempt, so it returns immediately without retrying.
Example 2
Input
// Success after retries
let attemptCount = 0;
const succeedOnThirdTry = () => {
attemptCount++;
return createMockApi(attemptCount === 3, 50);
};
retry(succeedOnThirdTry, 3, 200)
.then(result => console.log('Success:', result))
.catch(error => console.error('Error:', error));Output
Success: { statusCode: 200, message: 'Success!' }Explanation
The first two attempts fail, but the third attempt succeeds. The function waits 200ms between each retry.
Example 3
Input
// Failure after all retries
retry(() => createMockApi(false, 50), 3, 100)
.then(result => console.log('Success:', result))
.catch(error => console.log('Failed:', error));Output
Failed: { statusCode: 500, message: 'Internal Server Error on attempt' }Explanation
All 3 retry attempts fail. The function rejects with the error from the last attempt.
Constraints
- `asyncFn` is a function that returns a Promise
- `retries` must be a non-negative integer
- `delay` must be a non-negative number (milliseconds)
- If `retries` is 0, the function should only attempt once
Notes
- Consider using a loop or recursion to implement retries
- Use `setTimeout` or `Promise` with delay for waiting between retries
- Handle both resolved and rejected Promises correctly
Hints
Editorial: Fetch And Retry
Understanding Promise Retry
A retry function is a utility that automatically retries a failing async operation a specified number of times before giving up. This is essential for handling transient failures like network issues or server errors.
The Problem
APIs and network requests can fail temporarily due to:
- Network congestion or timeouts
- Server overload (503 errors)
- Rate limiting (429 errors)
- Temporary database issues
Instead of failing immediately, we want to retry the operation a few times with optional delays between attempts.
Implementation Strategy
- Track attempt count: Keep count of how many retries have been made
- Handle rejection: When the promise rejects, check if we have retries left
- Delay between retries: Optionally wait before retrying
- Recursive or iterative: Can be implemented either way
Solution 1: Recursive Approach
function retry(asyncFn, retries = 3, delay = 0) { return new Promise((resolve, reject) => { asyncFn() .then(resolve) .catch((error) => { if (retries <= 0) { reject(error); return; } setTimeout(() => { retry(asyncFn, retries - 1, delay) .then(resolve) .catch(reject); }, delay); }); }); }
Solution 2: Async/Await Approach
async function retry(asyncFn, retries = 3, delay = 0) { for (let i = 0; i <= retries; i++) { try { return await asyncFn(); } catch (error) { if (i === retries) { throw error; } if (delay > 0) { await new Promise(resolve => setTimeout(resolve, delay)); } } } }
Key Points
- Closure over attempt count: The retry logic maintains its own counter
- Promise chaining: Each retry creates a new promise chain
- Error propagation: Only throw/reject after all retries are exhausted
- Delay handling: Use
setTimeoutwrapped in a Promise for async delays
Advanced: Exponential Backoff
In production, you often want exponential backoff where delays increase with each retry:
async function retryWithBackoff(asyncFn, retries = 3, baseDelay = 1000) { for (let i = 0; i <= retries; i++) { try { return await asyncFn(); } catch (error) { if (i === retries) { throw error; } // Exponential backoff: 1s, 2s, 4s, 8s... const delay = baseDelay * Math.pow(2, i); // Add jitter to prevent thundering herd const jitter = Math.random() * 1000; await new Promise(resolve => setTimeout(resolve, delay + jitter) ); } } }
Time Complexity
- O(n) where n is the number of retries
Space Complexity
- O(1) for iterative approach
- O(n) for recursive approach (call stack)
Common Use Cases
- API calls: Retry failed HTTP requests
- Database connections: Retry on connection timeout
- File operations: Retry on temporary file locks
- Third-party services: Handle intermittent failures
Edge Cases to Consider
- Immediate success: Should work without any retries
- All retries fail: Should reject with the last error
- Zero retries: Should try exactly once
- Negative retries: Handle gracefully (treat as zero)
- Async function throws synchronously: Should still be caught
Production Considerations
async function retry(asyncFn, options = {}) { const { retries = 3, delay = 0, backoff = false, onRetry = () => {}, shouldRetry = () => true } = options; let lastError; for (let attempt = 0; attempt <= retries; attempt++) { try { return await asyncFn(attempt); } catch (error) { lastError = error; if (attempt === retries || !shouldRetry(error)) { throw error; } onRetry(error, attempt); const waitTime = backoff ? delay * Math.pow(2, attempt) : delay; if (waitTime > 0) { await new Promise(r => setTimeout(r, waitTime)); } } } throw lastError; }
This production version supports:
- Callbacks:
onRetryfor logging/metrics - Conditional retry:
shouldRetryto skip retries for certain errors (e.g., 404s) - Optional backoff: Exponential backoff when needed
- Attempt tracking: Pass attempt number to the async function
</>JavaScript
Loading editor…
Test Result
Run your code, or Submit to test it