How to implement retry of an asynchronous operation on error?

👨‍💻 Frontend Developer 🟠 May come up 🎚️ Medium
#JavaScript #Asynchronicity

Brief Answer

Retrying an asynchronous operation is a pattern where a failed asynchronous operation is automatically executed again after certain time intervals. This is especially useful when working with network requests that may temporarily fail due to network issues, server problems, or overload.

Main approaches:

  • Fixed delay — same interval between attempts
  • Exponential backoff — increasing interval between attempts
  • Attempt limiting — maximum number of retries

Full Answer

Implementing retry of asynchronous operations is an important technique for creating reliable web applications. It allows automatic recovery from temporary failures without user intervention.

Basic Retry Concepts

When implementing retry, several important parameters need to be considered:

// Basic retry function
async function retryOperation(operation, maxRetries = 3) {
  for (let i = 0; i <= maxRetries; i++) {
    try {
      return await operation();
    } catch (error) {
      if (i === maxRetries) throw error;
      // Pause before next attempt
      await delay(1000 * Math.pow(2, i));
    }
  }
}

Types of Retry Strategies

1. Fixed Delay

Same interval between all attempts:

function fixedDelayRetry(operation, retries, delayMs) {
  // Retry with fixed delay
}

2. Exponential Backoff

Interval increases in geometric progression:

// Delays: 1s, 2s, 4s, 8s...
const delay = 1000 * Math.pow(2, attempt);

3. With Jitter

Adding randomness to distribute load:

// Jitter reduces contention
const jitter = Math.random() * maxJitter;

Practical Examples

Simple HTTP Request Retry

async function fetchWithRetry(url, options = {}) {
  const { maxRetries = 3, retryDelay = 1000 } = options;
  
  for (let i = 0; i <= maxRetries; i++) {
    try {
      const response = await fetch(url);
      if (!response.ok) throw new Error('Request failed');
      return response;
    } catch (error) {
      if (i === maxRetries) throw error;
      await new Promise(resolve => 
        setTimeout(resolve, retryDelay * Math.pow(2, i))
      );
    }
  }
}

Smart Retry with Conditions

function smartRetry(operation, config) {
  const {
    maxRetries = 3,
    retryableErrors = ['NetworkError', 'TimeoutError']
  } = config;
  
  // Retry only for specific errors
}

Common Mistakes

1. Infinite Retry

// ❌ Can lead to blocking
while (true) {
  try {
    await operation();
    break;
  } catch (error) {
    // No attempt limit
  }
}
 
// ✅ With attempt limit
for (let i = 0; i < maxRetries; i++) {
  // Retry logic
}

2. Retrying All Errors

// ❌ Retry even fatal errors
if (error.status === 404) {
  // Resource doesn't exist, retry is useless
}
 
// ✅ Retry only temporary errors
if (error.status >= 500 || error.status === 429) {
  // Server errors and rate limiting
}

Best Practices

  1. Limit the number of attempts — avoid infinite loops
  2. Use exponential backoff — reduce server load
  3. Add jitter — distribute load over time
  4. Check error types — retry only temporary errors
  5. Log attempts — for debugging and monitoring

Compatibility

Retry implementation works in all modern JavaScript environments, including browsers and Node.js.

Key Retry Benefits

  1. Resilience — recovery from temporary failures
  2. User convenience — automatic recovery
  3. Reliability — reduced error count
  4. Performance — optimized network requests
  5. Scalability — server load management

Implementing retry of asynchronous operations is an important pattern for creating reliable web applications, helping to handle temporary network issues and server errors without user intervention.


Knowledge Check Task

Task

What will be the result of executing this code and how to improve it?

async function unreliableOperation() {
  if (Math.random() < 0.7) {
    throw new Error('Network error');
  }
  return 'Success';
}
 
async function retry(func, times) {
  try {
    return await func();
  } catch (error) {
    if (times <= 0) throw error;
    return retry(func, times - 1);
  }
}
 
retry(unreliableOperation, 3);
View answer

Answer: The result depends on the random factor, but in most cases there will be an “Network error”, as there is no delay between attempts.

Issues in the code:

  1. No delay between attempts — retries happen instantly
  2. No exponential backoff — server load remains constant
  3. No jitter — all clients will retry simultaneously
  4. No error type checking — all errors are retried

Improved version:

async function improvedRetry(func, maxRetries = 3) {
  for (let i = 0; i <= maxRetries; i++) {
    try {
      return await func();
    } catch (error) {
      // Retry only when maximum attempts reached
      if (i === maxRetries) throw error;
      
      // Exponential delay with jitter
      const delay = 1000 * Math.pow(2, i);
      const jitter = Math.random() * 500;
      await new Promise(resolve => 
        setTimeout(resolve, delay + jitter)
      );
    }
  }
}

Explanation: The improved version adds exponential delay and jitter, which prevents simultaneous retries from multiple clients and reduces server load. It also provides more predictable behavior and better error handling.


Want more articles to prepare for interviews? Subscribe to EasyAdvice, bookmark the site and improve yourself every day 💪