async-wrappers
Version:
A set of wrapper functions to perform debouncing, throttling, retrying etc.
233 lines (192 loc) • 4.71 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = exports.RetryStoppedError = exports.RetryCancelledError = exports.RetryError = void 0;
var _wait = _interopRequireDefault(require("./wait"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* @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
*/
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
*/
exports.RetryError = RetryError;
class RetryCancelledError extends RetryError {
constructor(message = 'Cancelled', error) {
super('cancelled', message, error);
}
}
/**
* The error given when retrying stops
*
* @category Retry
*/
exports.RetryCancelledError = RetryCancelledError;
class RetryStoppedError extends RetryError {
constructor(message = 'Stopped', error) {
super('stopped', message, error);
}
}
/**
* The return type of [[retry]]
*
* @category Retry
*/
exports.RetryStoppedError = RetryStoppedError;
/**
* 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 = (0, _wait.default)(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;
};
var _default = retry;
exports.default = _default;