DEV Community

Cover image for Implementing Retry Policy (React Native & Beyond)
Omotola Odumosu
Omotola Odumosu

Posted on

Implementing Retry Policy (React Native & Beyond)

A seamless user experience is one of the biggest factors in determining whether users stick with your app or abandon it. One underrated technique that helps achieve this is retry policy.

What is a Retry Policy?

A retry policy is a strategy where your application automatically retries a failed request instead of immediately showing an error to the user.

From the user’s perspective, everything just works:

“I made a request → I saw a loader → I got my result.”

But behind the scenes, your app may have retried that request multiple times before eventually succeeding.

Why Does This Matter?

Network calls are not always reliable. Failures can happen due to temporary server issues (5xx errors), network instability (timeouts, poor connectivity), rate limiting, or other transient failures.

If you immediately show an error, you force users to retry manually, restart their whole flow again, and potentially lose trust in your app.

A retry policy helps smooth over these temporary issues and improves overall user experience.

Retry policies are widely used across backend services (e.g., microservices communication), Cloud systems, distributed systems, Message queues, etc.

It’s a universal resilience pattern, used to improve performance and user experience. When implemented correctly, users never even know something went wrong, and that’s exactly the goal.

Implementation

const wait = (ms: number) =>
  new Promise((resolve) => setTimeout(resolve, ms));

type RetryOptions = {
  retries: number;
  delay: number;
  factor?: number; // exponential multiplier
};

async function retryRequest<T>(
  fn: () => Promise<T>,
  { retries, delay, factor = 2 }: RetryOptions
): Promise<T> {
  let attempt = 0;

  while (attempt <= retries) {
    try {
      return await fn();
    } catch (error: any) {
      const status = error?.response?.status;

      // Do not retry client errors (4xx)
      if (status >= 400 && status < 500) {
        throw error;
      }

      if (attempt === retries) {
        throw error;
      }

      const backoff = delay * Math.pow(factor, attempt);
      await wait(backoff);

      attempt++;
    }
  }

  throw new Error("Unexpected error");
}
Enter fullscreen mode Exit fullscreen mode

Understanding The Implementation Above

This implementation wraps any async operation (fn) with a retry mechanism.

  • retryRequest is a generic function, meaning it works with any return type while preserving type safety.

  • RetryOptions defines how many times to retry, the initial delay, and an optional exponential multiplier (factor).

  • The wait function simply pauses execution for a given time before retrying.

Implementation In Plain English

The function attempts to execute the provided operation, which is "fn". If it succeeds, the result is returned immediately, but if it fails, the error is inspected:

  • 4xx errors are not retried (these are client-side issues), e.g., the client input wrong details.

  • Other errors (e.g., 5xx or network failures) are retried.

Before each retry, the function waits using exponential backoff, where the delay increases after every attempt. This is to ensure we don't overburden the server. Exponential backoff helps give the server the time it needs to respond:

delay × factor^attempt

The process continues until the request succeeds or the maximum number of retries is reached. If all retries fail, the error is thrown.

Usage

const fetchUser = async () => {
  const response = await fetch("https://api.example.com/user");

  if (!response.ok) {
    const error: any = new Error("Request failed");
    error.response = { status: response.status };
    throw error;
  }

  return response.json();
};

const getUserWithRetry = async () => {
  try {
    const data = await retryRequest(fetchUser, {
      retries: 3,
      delay: 1000,
    });

    console.log("Success:", data);
  } catch (error) {
    console.log("Final failure:", error);
  }
};
Enter fullscreen mode Exit fullscreen mode

With this setup, you call the getUserWithRetry() function within your application whenever you need to fetch the user. If the request fails due to a transient issue, the retry policy automatically kicks in and attempts the request again behind the scenes.

In Conclusion

Retry policies are a simple but powerful way to improve reliability, reduce user frustration, and handle transient failures gracefully

It’s a small addition, but it can make your application feel significantly more stable and production-ready.


Did this post help simplify things for you?

If yes, drop a ❤️ or 🦄 reaction and follow me here on dev.to. I share more practical, plain-English breakdowns like this.

You can also connect with me on social media. I’d love to learn, share, and grow together with you!

LinkedIn: LinkedIn
Twitter: Twitter
Instagram: Instagram
Graphics Credit: Daniel Gustaw

Top comments (0)