UNPKG

@xiaohuohumax/x-fetch-plugin-retry

Version:
479 lines (389 loc) 11.7 kB
/*! * @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;