import { getGlobalThis } from '../web/getGlobalThis'; import type { FetchLike, FetchLikeInput } from './types'; type RequestDelayFunction = (attempt: number, error: Error | null, response: Response | null) => number; type RequestRetryOnFunction = ( attempt: number, error: Error | null, response: Response | null, ) => boolean | Promise; export interface FetchWithRetryOptions { fetch?: FetchLike; retries?: number; retryDelay?: number | RequestDelayFunction; retryOn?: number[] | RequestRetryOnFunction; } export function createFetchWithRetry({ fetch = getGlobalThis().fetch, retries = 3, retryDelay = 1000, retryOn = [], }: FetchWithRetryOptions = {}): FetchLike { // https://github.com/jonbern/fetch-retry/blob/master/index.js return function fetchRetry(input: FetchLikeInput, init?: RequestInit) { return new Promise((resolve, reject) => { var wrappedFetch = (attempt: number) => { // As of node 18, this is no longer needed since node comes with native support for fetch: /* istanbul ignore next */ var _input = typeof Request !== 'undefined' && input instanceof Request ? input.clone() : input; fetch(_input, init) .then((response) => { if (Array.isArray(retryOn) && retryOn.indexOf(response.status) === -1) { resolve(response); } else if (typeof retryOn === 'function') { try { return Promise.resolve(retryOn(attempt, null, response)) .then((retryOnResponse) => { if (retryOnResponse) { retry(attempt, null, response); } else { resolve(response); } }) .catch(reject); } catch (error) { reject(error); } } else { if (attempt < retries) { retry(attempt, null, response); } else { resolve(response); } } return; }) .catch((error) => { if (typeof retryOn === 'function') { try { // eslint-disable-next-line no-undef Promise.resolve(retryOn(attempt, error, null)) .then((retryOnResponse) => { if (retryOnResponse) { retry(attempt, error, null); } else { reject(error); } }) .catch((error) => { reject(error); }); } catch (error) { reject(error); } } else if (attempt < retries) { retry(attempt, error, null); } else { reject(error); } }); }; function retry(attempt: number, error: any, response: Response | null) { let delay = typeof retryDelay === 'function' ? retryDelay(attempt, error, response) : retryDelay; setTimeout(() => { wrappedFetch(++attempt); }, delay); } wrappedFetch(0); }); }; }