Repository: toss/es-toolkit
File: src/function/retry.ts (TypeScript)

Code before:
export async function retry<T>(func: () => Promise<T>, options: RetryOptions): Promise<T>;

/**
 * Retries a function that returns a promise with specified options.
 *
 * @template T
 * @param {() => Promise<T>} func - The function to retry. It should return a promise.
 * @param {number | RetryOptions} [_options] - Either the number of retries or an options object.
 * @returns {Promise<T>} A promise that resolves with the value of the successful function call.
 */
export async function retry<T>(func: () => Promise<T>, _options?: number | RetryOptions): Promise<T> {
  let delay: number | ((attempts: number) => number);
  let retries: number;
  let signal: AbortSignal | undefined;
  let shouldRetry: ((error: unknown, attempt: number) => boolean) | undefined;

  if (typeof _options === 'number') {
    delay = DEFAULT_DELAY;
    retries = _options;
    signal = undefined;
    shouldRetry = undefined;
  } else {
    delay = _options?.delay ?? DEFAULT_DELAY;
    retries = _options?.retries ?? DEFAULT_RETRIES;
    signal = _options?.signal;
    shouldRetry = _options?.shouldRetry;
  }

  let error;

  for (let attempts = 0; attempts < retries; attempts++) {
    if (signal?.aborted) {
      throw error ?? new Error(`The retry operation was aborted due to an abort signal.`);
    }

    try {
      return await func();
    } catch (err) {
      error = err;

      // Determine if we should retry based on the provided shouldRetry function
      if (shouldRetry && !shouldRetry(err, attempts)) {
        throw err;
      }

      const currentDelay = typeof delay === 'function' ? delay(attempts) : delay;
      await delayToolkit(currentDelay);
    }
  }

  throw error;

Review comment (suggestion):
```suggestion
    shouldRetry ??= _options.shouldRetry;
```

Code after:
 */
export async function retry<T>(func: () => Promise<T>, options: RetryOptions): Promise<T>;

/**
 * Retries a function that returns a promise with specified options.
 *
 * @template T
 * @param {() => Promise<T>} func - The function to retry. It should return a promise.
 * @param {number | RetryOptions} [_options] - Either the number of retries or an options object.
 * @returns {Promise<T>} A promise that resolves with the value of the successful function call.
 */
export async function retry<T>(func: () => Promise<T>, _options?: number | RetryOptions): Promise<T> {
  let delay: number | ((attempts: number) => number);
  let retries: number;
  let signal: AbortSignal | undefined;
  let shouldRetry: (error: unknown, attempt: number) => boolean;

  if (typeof _options === 'number') {
    delay = DEFAULT_DELAY;
    retries = _options;
    signal = undefined;
    shouldRetry = DEFAULT_SHOULD_RETRY;
  } else {
    delay = _options?.delay ?? DEFAULT_DELAY;
    retries = _options?.retries ?? DEFAULT_RETRIES;
    signal = _options?.signal;
    shouldRetry = _options?.shouldRetry ?? DEFAULT_SHOULD_RETRY;
  }

  let error;

  for (let attempts = 0; attempts < retries; attempts++) {
    if (signal?.aborted) {
      throw error ?? new Error(`The retry operation was aborted due to an abort signal.`);
    }

    try {
      return await func();
    } catch (err) {
      error = err;

      // Determine if we should retry based on the provided shouldRetry function
      if (!shouldRetry(err, attempts)) {
        throw err;
      }

      const currentDelay = typeof delay === 'function' ? delay(attempts) : delay;
      await delayToolkit(currentDelay);
    }
  }