UNPKG

async-wrappers

Version:

A set of wrapper functions to perform debouncing, throttling, retrying etc.

214 lines (182 loc) 4.24 kB
import wait from './wait'; /** * @internal */ const defaultDelays = [// 10 seconds 10 * 1000, // 1 Minute 60 * 1000, // 5 Minutes 5 * 60 * 1000, // 10 Minutes 10 * 60 * 1000]; /** * Function for determining a how long to wait after the given attempt * * @category Retry */ /** * @internal */ const getDelayFn = times => { if (typeof times === 'function') { return times; } if (typeof times === 'number') { return () => times; } return attempt => times[Math.min(attempt, times.length - 1)]; }; /** * A function used to determine if to stop for the given retry attempt. * * Errors thrown by this function will cause retry to reject with the * errorTypes of retry errors * * @category Retry */ /** * @internal */ const getStopFn = stop => { if (typeof stop === 'function') { return stop; } return attempt => attempt >= stop; }; /** * callback for [[retry]] to determine the arguments to * the function to retry. * @typeparam RetryFunction the type of function to retry. * * @category Retry */ /** * @internal */ const getArgsFn = args => { if (typeof args === 'function') { return args; } return () => args; }; /** * Types of retry errors * * @category Retry */ /** * Base Class for all retry errors * * @category Retry */ export class RetryError extends Error { /** * The type of error */ /** * The last error that occurred when retrying */ constructor(type, message, error) { super(message); this.type = type; this.error = error; } } /** * The error given when retrying is cancelled * * @category Retry */ export class RetryCancelledError extends RetryError { constructor(message = 'Cancelled', error) { super('cancelled', message, error); } } /** * The error given when retrying stops * * @category Retry */ export class RetryStoppedError extends RetryError { constructor(message = 'Stopped', error) { super('stopped', message, error); } } /** * The return type of [[retry]] * * @category Retry */ /** * Retry the wrapped function according to the * * @param func the function to retry. * @param delay a delay in milliseconds, an array of millisecond delays or * [[RetryDelayCallback|callback]] to determine the delay before the next * attempt. * * @param stop the number of attempts, or [[RetryStopCallback|callback]] to * determine when retry should stop retrying `func`. * @param args an array or [[RetryArgumentsCallback|callback]] to * provide arguments to `func` * * @category Wrapper */ const retry = (func, delay = defaultDelays, stop = 10, args = []) => { const getNextDelay = getDelayFn(delay); const shouldStop = getStopFn(stop); const getArgs = getArgsFn(args); let attempt = 0; let finished = false; let cancelled = false; let cancelReason; let waiting; let previousDelay = 0; let currentError; const afterAwait = () => { if (finished || !cancelled) { return; } throw new RetryCancelledError(cancelReason, currentError); }; const exec = async () => { while (!cancelled) { const args = await getArgs(attempt, previousDelay, currentError); afterAwait(); try { const value = await func(...args); finished = true; return value; } catch (error) { currentError = error; afterAwait(); // how long to wait after the last attempt const delay = await getNextDelay(attempt, previousDelay, error); afterAwait(); previousDelay = delay; attempt++; const stop = await shouldStop(attempt, delay, error); afterAwait(); // should we stop the next attempt? if (stop) { const reason = typeof stop === 'string' ? stop : undefined; finished = true; throw new RetryStoppedError(reason, error); } waiting = wait(delay); await waiting; waiting = undefined; afterAwait(); } } }; const result = exec(); result.cancel = reason => { if (finished || cancelled) { return; } cancelled = true; cancelReason = reason; if (waiting) { waiting.stop(); } }; return result; }; export default retry;