Skip to content

Instantly share code, notes, and snippets.

@karol-majewski
Created July 25, 2020 22:43
Show Gist options
  • Save karol-majewski/4f2aa94e7cf5ed6a11db8f411f1d8063 to your computer and use it in GitHub Desktop.
Save karol-majewski/4f2aa94e7cf5ed6a11db8f411f1d8063 to your computer and use it in GitHub Desktop.
Retry a Promise in TypeScript
const retry = (maxAttemps: number) => <T>(thunk: () => Promise<T>): () => Promise<T> => {
let attempts = 0;
const attempt = async (): Promise<T> => {
attempts++;
let result: T;
try {
result = await thunk();
} catch(error) {
if (attempts === maxAttemps) {
throw error;
}
result = await attempt()
}
return result;
}
return attempt;
}
@karol-majewski
Copy link
Author

export function assert(condition: boolean, message: string): asserts condition {
  if (!condition) {
    throw new Error(message);
  }
}

interface Options {
  /**
   * @default Infinity
   */
  maximumAttempts: number;
  /**
   * @default 0
   */
  timeoutMs: number;
  /**
   * What to do when the allowed retries are exhausted. For example,
   * invoke an error boundary by using `useErrorHandler` from 'react-error-boundary'.
   */
  handleError: (error: Error) => void;
}

export const retry =
  (options: Require<Partial<Options>, 'handleError'>) =>
  <T>(thunk: () => Promise<T>): (() => Promise<T>) => {
    const { maximumAttempts = Infinity, timeoutMs = 0, handleError } = options;

    assert(
      Object.is(maximumAttempts, Infinity) || (Number.isInteger(maximumAttempts) && maximumAttempts >= 0),
      'Expected maximum allowed number of attempts to be a natural number'
    );
    assert(timeoutMs >= 0, 'Expected timeoutMs to be zero or a positive number');

    let attempts = 0;
    const startTime = performance.now();

    const attempt = async (): Promise<T> => {
      attempts++;
      let result: T;
      try {
        result = await thunk();
      } catch (error) {
        const endTime = performance.now();

        const hasExceededMaximumAttempts = attempts > maximumAttempts;
        const hasDefinedTimeout = timeoutMs > 0;
        const hasTimedOut = endTime - timeoutMs > startTime;

        if (hasExceededMaximumAttempts || (hasDefinedTimeout && hasTimedOut)) {
          handleError(error as Error);
        }
        result = await attempt();
      }
      return result;
    };

    return attempt;
  };

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment