@xiaohuohumax/x-fetch-plugin-retry
Version:
x-fetch plugin retry package
479 lines (389 loc) • 11.7 kB
JavaScript
/*!
* @xiaohuohumax/x-fetch-plugin-retry v0.0.4
* Copyright (c) 2024 xiaohuohumax
* MIT License.
*/
'use strict';
var xFetchError = require('@xiaohuohumax/x-fetch-error');
require('@xiaohuohumax/x-fetch-request');
const DEFAULTS = {
enabled: false,
doNotRetry: [400, 401, 403, 404, 422, 451],
// promise-retry => retry
// https://github.com/IndigoUnited/node-promise-retry
// https://github.com/tim-kos/node-retry?tab=readme-ov-file#api
// 10 -> 3
retries: 3,
forever: false,
unref: false,
maxRetryTime: Infinity,
factor: 2,
minTimeout: 1e3,
maxTimeout: Infinity,
randomize: false
};
var version = "0.0.4";
const VERSION = version;
function getDefaultExportFromCjs (x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}
var errCode;
var hasRequiredErrCode;
function requireErrCode () {
if (hasRequiredErrCode) return errCode;
hasRequiredErrCode = 1;
function assign(obj, props) {
for (const key in props) {
Object.defineProperty(obj, key, {
value: props[key],
enumerable: true,
configurable: true,
});
}
return obj;
}
function createError(err, code, props) {
if (!err || typeof err === 'string') {
throw new TypeError('Please pass an Error to err-code');
}
if (!props) {
props = {};
}
if (typeof code === 'object') {
props = code;
code = undefined;
}
if (code != null) {
props.code = code;
}
try {
return assign(err, props);
} catch (_) {
props.message = err.message;
props.stack = err.stack;
const ErrClass = function () {};
ErrClass.prototype = Object.create(Object.getPrototypeOf(err));
return assign(new ErrClass(), props);
}
}
errCode = createError;
return errCode;
}
var retry$1 = {};
var retry_operation;
var hasRequiredRetry_operation;
function requireRetry_operation () {
if (hasRequiredRetry_operation) return retry_operation;
hasRequiredRetry_operation = 1;
function RetryOperation(timeouts, options) {
// Compatibility for the old (timeouts, retryForever) signature
if (typeof options === 'boolean') {
options = { forever: options };
}
this._originalTimeouts = JSON.parse(JSON.stringify(timeouts));
this._timeouts = timeouts;
this._options = options || {};
this._maxRetryTime = options && options.maxRetryTime || Infinity;
this._fn = null;
this._errors = [];
this._attempts = 1;
this._operationTimeout = null;
this._operationTimeoutCb = null;
this._timeout = null;
this._operationStart = null;
if (this._options.forever) {
this._cachedTimeouts = this._timeouts.slice(0);
}
}
retry_operation = RetryOperation;
RetryOperation.prototype.reset = function() {
this._attempts = 1;
this._timeouts = this._originalTimeouts;
};
RetryOperation.prototype.stop = function() {
if (this._timeout) {
clearTimeout(this._timeout);
}
this._timeouts = [];
this._cachedTimeouts = null;
};
RetryOperation.prototype.retry = function(err) {
if (this._timeout) {
clearTimeout(this._timeout);
}
if (!err) {
return false;
}
var currentTime = new Date().getTime();
if (err && currentTime - this._operationStart >= this._maxRetryTime) {
this._errors.unshift(new Error('RetryOperation timeout occurred'));
return false;
}
this._errors.push(err);
var timeout = this._timeouts.shift();
if (timeout === undefined) {
if (this._cachedTimeouts) {
// retry forever, only keep last error
this._errors.splice(this._errors.length - 1, this._errors.length);
this._timeouts = this._cachedTimeouts.slice(0);
timeout = this._timeouts.shift();
} else {
return false;
}
}
var self = this;
var timer = setTimeout(function() {
self._attempts++;
if (self._operationTimeoutCb) {
self._timeout = setTimeout(function() {
self._operationTimeoutCb(self._attempts);
}, self._operationTimeout);
if (self._options.unref) {
self._timeout.unref();
}
}
self._fn(self._attempts);
}, timeout);
if (this._options.unref) {
timer.unref();
}
return true;
};
RetryOperation.prototype.attempt = function(fn, timeoutOps) {
this._fn = fn;
if (timeoutOps) {
if (timeoutOps.timeout) {
this._operationTimeout = timeoutOps.timeout;
}
if (timeoutOps.cb) {
this._operationTimeoutCb = timeoutOps.cb;
}
}
var self = this;
if (this._operationTimeoutCb) {
this._timeout = setTimeout(function() {
self._operationTimeoutCb();
}, self._operationTimeout);
}
this._operationStart = new Date().getTime();
this._fn(this._attempts);
};
RetryOperation.prototype.try = function(fn) {
console.log('Using RetryOperation.try() is deprecated');
this.attempt(fn);
};
RetryOperation.prototype.start = function(fn) {
console.log('Using RetryOperation.start() is deprecated');
this.attempt(fn);
};
RetryOperation.prototype.start = RetryOperation.prototype.try;
RetryOperation.prototype.errors = function() {
return this._errors;
};
RetryOperation.prototype.attempts = function() {
return this._attempts;
};
RetryOperation.prototype.mainError = function() {
if (this._errors.length === 0) {
return null;
}
var counts = {};
var mainError = null;
var mainErrorCount = 0;
for (var i = 0; i < this._errors.length; i++) {
var error = this._errors[i];
var message = error.message;
var count = (counts[message] || 0) + 1;
counts[message] = count;
if (count >= mainErrorCount) {
mainError = error;
mainErrorCount = count;
}
}
return mainError;
};
return retry_operation;
}
var hasRequiredRetry$1;
function requireRetry$1 () {
if (hasRequiredRetry$1) return retry$1;
hasRequiredRetry$1 = 1;
(function (exports) {
var RetryOperation = requireRetry_operation();
exports.operation = function(options) {
var timeouts = exports.timeouts(options);
return new RetryOperation(timeouts, {
forever: options && options.forever,
unref: options && options.unref,
maxRetryTime: options && options.maxRetryTime
});
};
exports.timeouts = function(options) {
if (options instanceof Array) {
return [].concat(options);
}
var opts = {
retries: 10,
factor: 2,
minTimeout: 1 * 1000,
maxTimeout: Infinity,
randomize: false
};
for (var key in options) {
opts[key] = options[key];
}
if (opts.minTimeout > opts.maxTimeout) {
throw new Error('minTimeout is greater than maxTimeout');
}
var timeouts = [];
for (var i = 0; i < opts.retries; i++) {
timeouts.push(this.createTimeout(i, opts));
}
if (options && options.forever && !timeouts.length) {
timeouts.push(this.createTimeout(i, opts));
}
// sort the array numerically ascending
timeouts.sort(function(a,b) {
return a - b;
});
return timeouts;
};
exports.createTimeout = function(attempt, opts) {
var random = (opts.randomize)
? (Math.random() + 1)
: 1;
var timeout = Math.round(random * opts.minTimeout * Math.pow(opts.factor, attempt));
timeout = Math.min(timeout, opts.maxTimeout);
return timeout;
};
exports.wrap = function(obj, options, methods) {
if (options instanceof Array) {
methods = options;
options = null;
}
if (!methods) {
methods = [];
for (var key in obj) {
if (typeof obj[key] === 'function') {
methods.push(key);
}
}
}
for (var i = 0; i < methods.length; i++) {
var method = methods[i];
var original = obj[method];
obj[method] = function retryWrapper(original) {
var op = exports.operation(options);
var args = Array.prototype.slice.call(arguments, 1);
var callback = args.pop();
args.push(function(err) {
if (op.retry(err)) {
return;
}
if (err) {
arguments[0] = op.mainError();
}
callback.apply(this, arguments);
});
op.attempt(function() {
original.apply(obj, args);
});
}.bind(obj, original);
obj[method].options = options;
}
};
} (retry$1));
return retry$1;
}
var retry;
var hasRequiredRetry;
function requireRetry () {
if (hasRequiredRetry) return retry;
hasRequiredRetry = 1;
retry = requireRetry$1();
return retry;
}
var promiseRetry_1;
var hasRequiredPromiseRetry;
function requirePromiseRetry () {
if (hasRequiredPromiseRetry) return promiseRetry_1;
hasRequiredPromiseRetry = 1;
var errcode = requireErrCode();
var retry = requireRetry();
var hasOwn = Object.prototype.hasOwnProperty;
function isRetryError(err) {
return err && err.code === 'EPROMISERETRY' && hasOwn.call(err, 'retried');
}
function promiseRetry(fn, options) {
var temp;
var operation;
if (typeof fn === 'object' && typeof options === 'function') {
// Swap options and fn when using alternate signature (options, fn)
temp = options;
options = fn;
fn = temp;
}
operation = retry.operation(options);
return new Promise(function (resolve, reject) {
operation.attempt(function (number) {
Promise.resolve()
.then(function () {
return fn(function (err) {
if (isRetryError(err)) {
err = err.retried;
}
throw errcode(new Error('Retrying'), 'EPROMISERETRY', { retried: err });
}, number);
})
.then(resolve, function (err) {
if (isRetryError(err)) {
err = err.retried;
if (operation.retry(err || new Error())) {
return;
}
}
reject(err);
});
});
});
}
promiseRetry_1 = promiseRetry;
return promiseRetry_1;
}
var promiseRetryExports = requirePromiseRetry();
var promiseRetry = /*@__PURE__*/getDefaultExportFromCjs(promiseRetryExports);
function retryPlugin(xFetch, options) {
let state = Object.assign(DEFAULTS, options.retry);
xFetch.request.hook.wrap("request", async (request, options2) => {
state = Object.assign(state, options2.retry);
if (!state.enabled) {
return await request(options2);
}
let retryCount = 0;
return await promiseRetry(async (retry, _number) => {
retryCount++;
let response;
try {
response = await (retryCount === 1 ? request(options2) : xFetch.request.hook("retry", request, options2));
} catch (error) {
if (error instanceof Error && (error.name === "AbortError" || error instanceof xFetchError.XFetchTimeoutError)) {
throw error;
}
return retry(error);
}
if (!state.doNotRetry.includes(response.status) && response.status >= 400) {
return retry(new xFetchError.XFetchRequestError(response.statusText, {
status: response.status,
statusText: response.statusText,
request: options2,
response
}));
}
return response;
}, state);
});
return {};
}
exports.DEFAULTS = DEFAULTS;
exports.VERSION = VERSION;
exports.retryPlugin = retryPlugin;