Home Reference Source

src/hofs/onErrorRetry.js

import { defer, from, throwError, of } from "rxjs";
import { retryWhen, mergeMap, delay } from "rxjs/operators";

const defaultOptions = { maxRetry: 3, delayMs: 500 };

const get = (obj, field, defaultValue) => (obj || {})[field] || defaultValue;

/**
 * Creat a fetch which retries when:
 * + GET with 5XX error
 * + Network error
 *
 * Before import this, you have to install `rxjs` to your project:
 * ```
 * npm install rxjs
 * ```
 * @requires `rxjs`
 * @example
 * import {onErrorRetry} from 'higher-order-fetch/hofs/onErrorRetry'
 *
 * const fetch = onErrorRetry({
 *  maxRetry: 5,
 *  delayMs: 3000
 * })(window.fetch)
 *
 * fetch('http://example.com')// if request error, will retry maximum 5 times before resolve
 * .then(response => {
 *  // `response` is the last response after all retries or success.
 * })
 * @param {{maxRetry: number, delayMs: number}} [options]
 * @returns {function} a fetch-like function
 */
export const onErrorRetry = options => fetch => (input, init = {}) => {
  const maxRetry = get(options, "maxRetry", defaultOptions.maxRetry);
  const delayMs = get(options, "delayMs", defaultOptions.delayMs);

  if (!init.method || init.method.toUpperCase() === "GET") {
    let count = 0;
    return defer(() => {
      return from(
        fetch(input, init).then(resp => {
          if ((resp.status + "").startsWith("5")) throw resp;
          return resp;
        })
      );
    })
      .pipe(
        retryWhen(errors => {
          return errors.pipe(
            mergeMap(error => {
              if (++count >= maxRetry) {
                return throwError(error);
              } else {
                return of(error).pipe(delay(delayMs));
              }
            })
          );
        })
      )
      .toPromise()
      .then(
        resp => resp,
        resp => {
          if (resp.status === 500) return resp;
          throw resp;
        }
      );
  }
  return fetch(input, init);
};