Initial commit

This commit is contained in:
2025-03-07 19:22:02 +01:00
commit 4a98255d83
55743 changed files with 5280367 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
Copyright 2019 Softonic International S.A.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+85
View File
@@ -0,0 +1,85 @@
# axios-retry
[![Node.js CI](https://github.com/softonic/axios-retry/actions/workflows/node.js.yml/badge.svg)](https://github.com/softonic/axios-retry/actions/workflows/node.js.yml)
Axios plugin that intercepts failed requests and retries them whenever possible.
## Installation
```bash
npm install axios-retry
```
## Usage
```js
// CommonJS
// const axiosRetry = require('axios-retry');
// ES6
import axiosRetry from 'axios-retry';
axiosRetry(axios, { retries: 3 });
axios.get('http://example.com/test') // The first request fails and the second returns 'ok'
.then(result => {
result.data; // 'ok'
});
// Exponential back-off retry delay between requests
axiosRetry(axios, { retryDelay: axiosRetry.exponentialDelay });
// Custom retry delay
axiosRetry(axios, { retryDelay: (retryCount) => {
return retryCount * 1000;
}});
// Works with custom axios instances
const client = axios.create({ baseURL: 'http://example.com' });
axiosRetry(client, { retries: 3 });
client.get('/test') // The first request fails and the second returns 'ok'
.then(result => {
result.data; // 'ok'
});
// Allows request-specific configuration
client
.get('/test', {
'axios-retry': {
retries: 0
}
})
.catch(error => { // The first request fails
error !== undefined
});
```
**Note:** Unless `shouldResetTimeout` is set, the plugin interprets the request timeout as a global value, so it is not used for each retry but for the whole request lifecycle.
## Options
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| retries | `Number` | `3` | The number of times to retry before failing. 1 = One retry after first failure |
| retryCondition | `Function` | `isNetworkOrIdempotentRequestError` | A callback to further control if a request should be retried. By default, it retries if it is a network error or a 5xx error on an idempotent request (GET, HEAD, OPTIONS, PUT or DELETE). |
| shouldResetTimeout | `Boolean` | false | Defines if the timeout should be reset between retries |
| retryDelay | `Function` | `function noDelay() { return 0; }` | A callback to further control the delay in milliseconds between retried requests. By default there is no delay between retries. Another option is exponentialDelay ([Exponential Backoff](https://developers.google.com/analytics/devguides/reporting/core/v3/errors#backoff)). The function is passed `retryCount` and `error`. |
| onRetry | `Function` | `function onRetry(retryCount, error, requestConfig) { return; }` | A callback to notify when a retry is about to occur. Useful for tracing and you can any async process for example refresh a token on 401. By default nothing will occur. The function is passed `retryCount`, `error`, and `requestConfig`. |
## Testing
Clone the repository and execute:
```bash
npm test
```
## Contribute
1. Fork it: `git clone https://github.com/softonic/axios-retry.git`
2. Create your feature branch: `git checkout -b feature/my-new-feature`
3. Commit your changes: `git commit -am 'Added some feature'`
4. Check the build: `npm run build`
4. Push to the branch: `git push origin my-new-feature`
5. Submit a pull request :D
+270
View File
@@ -0,0 +1,270 @@
import isRetryAllowed from 'is-retry-allowed';
export const namespace = 'axios-retry';
/**
* @param {Error} error
* @return {boolean}
*/
export function isNetworkError(error) {
const CODE_EXCLUDE_LIST = ['ERR_CANCELED', 'ECONNABORTED'];
return (
!error.response &&
Boolean(error.code) && // Prevents retrying cancelled requests
!CODE_EXCLUDE_LIST.includes(error.code) && // Prevents retrying timed out & cancelled requests
isRetryAllowed(error) // Prevents retrying unsafe errors
);
}
const SAFE_HTTP_METHODS = ['get', 'head', 'options'];
const IDEMPOTENT_HTTP_METHODS = SAFE_HTTP_METHODS.concat(['put', 'delete']);
/**
* @param {Error} error
* @return {boolean}
*/
export function isRetryableError(error) {
return (
error.code !== 'ECONNABORTED' &&
(!error.response || (error.response.status >= 500 && error.response.status <= 599))
);
}
/**
* @param {Error} error
* @return {boolean}
*/
export function isSafeRequestError(error) {
if (!error.config) {
// Cannot determine if the request can be retried
return false;
}
return isRetryableError(error) && SAFE_HTTP_METHODS.indexOf(error.config.method) !== -1;
}
/**
* @param {Error} error
* @return {boolean}
*/
export function isIdempotentRequestError(error) {
if (!error.config) {
// Cannot determine if the request can be retried
return false;
}
return isRetryableError(error) && IDEMPOTENT_HTTP_METHODS.indexOf(error.config.method) !== -1;
}
/**
* @param {Error} error
* @return {boolean}
*/
export function isNetworkOrIdempotentRequestError(error) {
return isNetworkError(error) || isIdempotentRequestError(error);
}
/**
* @return {number} - delay in milliseconds, always 0
*/
function noDelay() {
return 0;
}
/**
* Set delayFactor 1000 for an exponential delay to occur on the order
* of seconds
* @param {number} [retryNumber=0]
* @param {Error} error - unused; for existing API of retryDelay callback
* @param {number} [delayFactor=100] milliseconds
* @return {number} - delay in milliseconds
*/
export function exponentialDelay(retryNumber = 0, error, delayFactor = 100) {
const delay = Math.pow(2, retryNumber) * delayFactor;
const randomSum = delay * 0.2 * Math.random(); // 0-20% of the delay
return delay + randomSum;
}
/** @type {IAxiosRetryConfig} */
export const DEFAULT_OPTIONS = {
retries: 3,
retryCondition: isNetworkOrIdempotentRequestError,
retryDelay: noDelay,
shouldResetTimeout: false,
onRetry: () => {}
};
/**
* Returns the axios-retry options for the current request
* @param {AxiosRequestConfig} config
* @param {IAxiosRetryConfig} defaultOptions
* @return {IAxiosRetryConfigExtended}
*/
function getRequestOptions(config, defaultOptions) {
return { ...DEFAULT_OPTIONS, ...defaultOptions, ...config[namespace] };
}
/**
* Initializes and returns the retry state for the given request/config
* @param {AxiosRequestConfig} config
* @param {IAxiosRetryConfig} defaultOptions
* @return {IAxiosRetryConfigExtended}
*/
function getCurrentState(config, defaultOptions) {
const currentState = getRequestOptions(config, defaultOptions);
currentState.retryCount = currentState.retryCount || 0;
config[namespace] = currentState;
return currentState;
}
/**
* @param {Axios} axios
* @param {AxiosRequestConfig} config
*/
function fixConfig(axios, config) {
if (axios.defaults.agent === config.agent) {
delete config.agent;
}
if (axios.defaults.httpAgent === config.httpAgent) {
delete config.httpAgent;
}
if (axios.defaults.httpsAgent === config.httpsAgent) {
delete config.httpsAgent;
}
}
/**
* Checks retryCondition if request can be retried. Handles it's returning value or Promise.
* @param {IAxiosRetryConfigExtended} currentState
* @param {Error} error
* @return {Promise<boolean>}
*/
async function shouldRetry(currentState, error) {
const { retries, retryCondition } = currentState;
const shouldRetryOrPromise = currentState.retryCount < retries && retryCondition(error);
// This could be a promise
if (typeof shouldRetryOrPromise === 'object') {
try {
const shouldRetryPromiseResult = await shouldRetryOrPromise;
// keep return true unless shouldRetryPromiseResult return false for compatibility
return shouldRetryPromiseResult !== false;
} catch (_err) {
return false;
}
}
return shouldRetryOrPromise;
}
/**
* Adds response interceptors to an axios instance to retry requests failed due to network issues
*
* @example
*
* import axios from 'axios';
*
* axiosRetry(axios, { retries: 3 });
*
* axios.get('http://example.com/test') // The first request fails and the second returns 'ok'
* .then(result => {
* result.data; // 'ok'
* });
*
* // Exponential back-off retry delay between requests
* axiosRetry(axios, { retryDelay : axiosRetry.exponentialDelay});
*
* // Custom retry delay
* axiosRetry(axios, { retryDelay : (retryCount) => {
* return retryCount * 1000;
* }});
*
* // Also works with custom axios instances
* const client = axios.create({ baseURL: 'http://example.com' });
* axiosRetry(client, { retries: 3 });
*
* client.get('/test') // The first request fails and the second returns 'ok'
* .then(result => {
* result.data; // 'ok'
* });
*
* // Allows request-specific configuration
* client
* .get('/test', {
* 'axios-retry': {
* retries: 0
* }
* })
* .catch(error => { // The first request fails
* error !== undefined
* });
*
* @param {Axios} axios An axios instance (the axios object or one created from axios.create)
* @param {Object} [defaultOptions]
* @param {number} [defaultOptions.retries=3] Number of retries
* @param {boolean} [defaultOptions.shouldResetTimeout=false]
* Defines if the timeout should be reset between retries
* @param {Function} [defaultOptions.retryCondition=isNetworkOrIdempotentRequestError]
* A function to determine if the error can be retried
* @param {Function} [defaultOptions.retryDelay=noDelay]
* A function to determine the delay between retry requests
* @param {Function} [defaultOptions.onRetry=()=>{}]
* A function to get notified when a retry occurs
* @return {{ requestInterceptorId: number, responseInterceptorId: number }}
* The ids of the interceptors added to the request and to the response (so they can be ejected at a later time)
*/
export default function axiosRetry(axios, defaultOptions) {
const requestInterceptorId = axios.interceptors.request.use((config) => {
const currentState = getCurrentState(config, defaultOptions);
currentState.lastRequestTime = Date.now();
return config;
});
const responseInterceptorId = axios.interceptors.response.use(null, async (error) => {
const { config } = error;
// If we have no information to retry the request
if (!config) {
return Promise.reject(error);
}
const currentState = getCurrentState(config, defaultOptions);
if (await shouldRetry(currentState, error)) {
currentState.retryCount += 1;
const { retryDelay, shouldResetTimeout, onRetry } = currentState;
const delay = retryDelay(currentState.retryCount, error);
// Axios fails merging this configuration to the default configuration because it has an issue
// with circular structures: https://github.com/mzabriskie/axios/issues/370
fixConfig(axios, config);
if (!shouldResetTimeout && config.timeout && currentState.lastRequestTime) {
const lastRequestDuration = Date.now() - currentState.lastRequestTime;
const timeout = config.timeout - lastRequestDuration - delay;
if (timeout <= 0) {
return Promise.reject(error);
}
config.timeout = timeout;
}
config.transformRequest = [(data) => data];
await onRetry(currentState.retryCount, error, config);
return new Promise((resolve) => setTimeout(() => resolve(axios(config)), delay));
}
return Promise.reject(error);
});
return { requestInterceptorId, responseInterceptorId };
}
// Compatibility with CommonJS
axiosRetry.isNetworkError = isNetworkError;
axiosRetry.isSafeRequestError = isSafeRequestError;
axiosRetry.isIdempotentRequestError = isIdempotentRequestError;
axiosRetry.isNetworkOrIdempotentRequestError = isNetworkOrIdempotentRequestError;
axiosRetry.exponentialDelay = exponentialDelay;
axiosRetry.isRetryableError = isRetryableError;
+97
View File
@@ -0,0 +1,97 @@
import * as axios from 'axios';
export = IAxiosRetry;
export as namespace axiosRetry;
declare const IAxiosRetry: IAxiosRetry.AxiosRetry;
declare namespace IAxiosRetry {
export interface AxiosRetry {
(
axios: axios.AxiosStatic | axios.AxiosInstance,
axiosRetryConfig?: IAxiosRetryConfig
): IAxiosRetryReturn;
isNetworkError(error: Error): boolean;
isRetryableError(error: Error): boolean;
isSafeRequestError(error: Error): boolean;
isIdempotentRequestError(error: Error): boolean;
isNetworkOrIdempotentRequestError(error: Error): boolean;
exponentialDelay(retryNumber?: number, error?: Error, delayFactor?: number): number;
}
export interface IAxiosRetryConfig {
/**
* The number of times to retry before failing
* default: 3
*
* @type {number}
*/
retries?: number;
/**
* Defines if the timeout should be reset between retries
* default: false
*
* @type {boolean}
*/
shouldResetTimeout?: boolean;
/**
* A callback to further control if a request should be retried.
* default: it retries if it is a network error or a 5xx error on an idempotent request (GET, HEAD, OPTIONS, PUT or DELETE).
*
* @type {Function}
*/
retryCondition?: (error: axios.AxiosError) => boolean | Promise<boolean>;
/**
* A callback to further control the delay between retry requests. By default there is no delay.
*
* @type {Function}
*/
retryDelay?: (retryCount: number, error: axios.AxiosError) => number;
/**
* A callback to get notified when a retry occurs, the number of times it has occurre, and the error
*
* @type {Function}
*/
onRetry?: (
retryCount: number,
error: axios.AxiosError,
requestConfig: axios.AxiosRequestConfig
) => void;
}
export interface IAxiosRetryConfigExtended extends IAxiosRetryConfig {
/**
* The number of times the request was retried
*
* @type {number}
*/
retryCount?: number;
/**
* The last time the request was retried (timestamp in milliseconds)
*
* @type {number}
*/
lastRequestTime?: number;
}
export interface IAxiosRetryReturn {
/**
* The interceptorId for the request interceptor
*
* @type {number}
*/
requestInterceptorId: number;
/**
* The interceptorId for the response interceptor
*
* @type {number}
*/
responseInterceptorId: number;
}
}
declare module 'axios' {
export interface AxiosRequestConfig {
'axios-retry'?: IAxiosRetry.IAxiosRetryConfigExtended;
}
}
+4
View File
@@ -0,0 +1,4 @@
const axiosRetry = require('./lib/cjs/index').default;
module.exports = axiosRetry;
module.exports.default = axiosRetry;
+386
View File
@@ -0,0 +1,386 @@
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.isNetworkError = isNetworkError;
exports.isRetryableError = isRetryableError;
exports.isSafeRequestError = isSafeRequestError;
exports.isIdempotentRequestError = isIdempotentRequestError;
exports.isNetworkOrIdempotentRequestError = isNetworkOrIdempotentRequestError;
exports.exponentialDelay = exponentialDelay;
exports.default = axiosRetry;
exports.DEFAULT_OPTIONS = exports.namespace = void 0;
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof"));
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _isRetryAllowed = _interopRequireDefault(require("is-retry-allowed"));
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) { symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); } keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
var namespace = 'axios-retry';
/**
* @param {Error} error
* @return {boolean}
*/
exports.namespace = namespace;
function isNetworkError(error) {
var CODE_EXCLUDE_LIST = ['ERR_CANCELED', 'ECONNABORTED'];
return !error.response && Boolean(error.code) && // Prevents retrying cancelled requests
!CODE_EXCLUDE_LIST.includes(error.code) && // Prevents retrying timed out & cancelled requests
(0, _isRetryAllowed.default)(error) // Prevents retrying unsafe errors
;
}
var SAFE_HTTP_METHODS = ['get', 'head', 'options'];
var IDEMPOTENT_HTTP_METHODS = SAFE_HTTP_METHODS.concat(['put', 'delete']);
/**
* @param {Error} error
* @return {boolean}
*/
function isRetryableError(error) {
return error.code !== 'ECONNABORTED' && (!error.response || error.response.status >= 500 && error.response.status <= 599);
}
/**
* @param {Error} error
* @return {boolean}
*/
function isSafeRequestError(error) {
if (!error.config) {
// Cannot determine if the request can be retried
return false;
}
return isRetryableError(error) && SAFE_HTTP_METHODS.indexOf(error.config.method) !== -1;
}
/**
* @param {Error} error
* @return {boolean}
*/
function isIdempotentRequestError(error) {
if (!error.config) {
// Cannot determine if the request can be retried
return false;
}
return isRetryableError(error) && IDEMPOTENT_HTTP_METHODS.indexOf(error.config.method) !== -1;
}
/**
* @param {Error} error
* @return {boolean}
*/
function isNetworkOrIdempotentRequestError(error) {
return isNetworkError(error) || isIdempotentRequestError(error);
}
/**
* @return {number} - delay in milliseconds, always 0
*/
function noDelay() {
return 0;
}
/**
* Set delayFactor 1000 for an exponential delay to occur on the order
* of seconds
* @param {number} [retryNumber=0]
* @param {Error} error - unused; for existing API of retryDelay callback
* @param {number} [delayFactor=100] milliseconds
* @return {number} - delay in milliseconds
*/
function exponentialDelay() {
var retryNumber = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
var error = arguments.length > 1 ? arguments[1] : undefined;
var delayFactor = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 100;
var delay = Math.pow(2, retryNumber) * delayFactor;
var randomSum = delay * 0.2 * Math.random(); // 0-20% of the delay
return delay + randomSum;
}
/** @type {IAxiosRetryConfig} */
var DEFAULT_OPTIONS = {
retries: 3,
retryCondition: isNetworkOrIdempotentRequestError,
retryDelay: noDelay,
shouldResetTimeout: false,
onRetry: function onRetry() {}
};
/**
* Returns the axios-retry options for the current request
* @param {AxiosRequestConfig} config
* @param {IAxiosRetryConfig} defaultOptions
* @return {IAxiosRetryConfigExtended}
*/
exports.DEFAULT_OPTIONS = DEFAULT_OPTIONS;
function getRequestOptions(config, defaultOptions) {
return _objectSpread(_objectSpread(_objectSpread({}, DEFAULT_OPTIONS), defaultOptions), config[namespace]);
}
/**
* Initializes and returns the retry state for the given request/config
* @param {AxiosRequestConfig} config
* @param {IAxiosRetryConfig} defaultOptions
* @return {IAxiosRetryConfigExtended}
*/
function getCurrentState(config, defaultOptions) {
var currentState = getRequestOptions(config, defaultOptions);
currentState.retryCount = currentState.retryCount || 0;
config[namespace] = currentState;
return currentState;
}
/**
* @param {Axios} axios
* @param {AxiosRequestConfig} config
*/
function fixConfig(axios, config) {
if (axios.defaults.agent === config.agent) {
delete config.agent;
}
if (axios.defaults.httpAgent === config.httpAgent) {
delete config.httpAgent;
}
if (axios.defaults.httpsAgent === config.httpsAgent) {
delete config.httpsAgent;
}
}
/**
* Checks retryCondition if request can be retried. Handles it's returning value or Promise.
* @param {IAxiosRetryConfigExtended} currentState
* @param {Error} error
* @return {Promise<boolean>}
*/
function shouldRetry(_x, _x2) {
return _shouldRetry.apply(this, arguments);
}
/**
* Adds response interceptors to an axios instance to retry requests failed due to network issues
*
* @example
*
* import axios from 'axios';
*
* axiosRetry(axios, { retries: 3 });
*
* axios.get('http://example.com/test') // The first request fails and the second returns 'ok'
* .then(result => {
* result.data; // 'ok'
* });
*
* // Exponential back-off retry delay between requests
* axiosRetry(axios, { retryDelay : axiosRetry.exponentialDelay});
*
* // Custom retry delay
* axiosRetry(axios, { retryDelay : (retryCount) => {
* return retryCount * 1000;
* }});
*
* // Also works with custom axios instances
* const client = axios.create({ baseURL: 'http://example.com' });
* axiosRetry(client, { retries: 3 });
*
* client.get('/test') // The first request fails and the second returns 'ok'
* .then(result => {
* result.data; // 'ok'
* });
*
* // Allows request-specific configuration
* client
* .get('/test', {
* 'axios-retry': {
* retries: 0
* }
* })
* .catch(error => { // The first request fails
* error !== undefined
* });
*
* @param {Axios} axios An axios instance (the axios object or one created from axios.create)
* @param {Object} [defaultOptions]
* @param {number} [defaultOptions.retries=3] Number of retries
* @param {boolean} [defaultOptions.shouldResetTimeout=false]
* Defines if the timeout should be reset between retries
* @param {Function} [defaultOptions.retryCondition=isNetworkOrIdempotentRequestError]
* A function to determine if the error can be retried
* @param {Function} [defaultOptions.retryDelay=noDelay]
* A function to determine the delay between retry requests
* @param {Function} [defaultOptions.onRetry=()=>{}]
* A function to get notified when a retry occurs
* @return {{ requestInterceptorId: number, responseInterceptorId: number }}
* The ids of the interceptors added to the request and to the response (so they can be ejected at a later time)
*/
function _shouldRetry() {
_shouldRetry = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee2(currentState, error) {
var retries, retryCondition, shouldRetryOrPromise, shouldRetryPromiseResult;
return _regenerator.default.wrap(function _callee2$(_context2) {
while (1) {
switch (_context2.prev = _context2.next) {
case 0:
retries = currentState.retries, retryCondition = currentState.retryCondition;
shouldRetryOrPromise = currentState.retryCount < retries && retryCondition(error); // This could be a promise
if (!((0, _typeof2.default)(shouldRetryOrPromise) === 'object')) {
_context2.next = 13;
break;
}
_context2.prev = 3;
_context2.next = 6;
return shouldRetryOrPromise;
case 6:
shouldRetryPromiseResult = _context2.sent;
return _context2.abrupt("return", shouldRetryPromiseResult !== false);
case 10:
_context2.prev = 10;
_context2.t0 = _context2["catch"](3);
return _context2.abrupt("return", false);
case 13:
return _context2.abrupt("return", shouldRetryOrPromise);
case 14:
case "end":
return _context2.stop();
}
}
}, _callee2, null, [[3, 10]]);
}));
return _shouldRetry.apply(this, arguments);
}
function axiosRetry(axios, defaultOptions) {
var requestInterceptorId = axios.interceptors.request.use(function (config) {
var currentState = getCurrentState(config, defaultOptions);
currentState.lastRequestTime = Date.now();
return config;
});
var responseInterceptorId = axios.interceptors.response.use(null, /*#__PURE__*/function () {
var _ref = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(error) {
var config, currentState, retryDelay, shouldResetTimeout, onRetry, delay, lastRequestDuration, timeout;
return _regenerator.default.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
config = error.config; // If we have no information to retry the request
if (config) {
_context.next = 3;
break;
}
return _context.abrupt("return", Promise.reject(error));
case 3:
currentState = getCurrentState(config, defaultOptions);
_context.next = 6;
return shouldRetry(currentState, error);
case 6:
if (!_context.sent) {
_context.next = 21;
break;
}
currentState.retryCount += 1;
retryDelay = currentState.retryDelay, shouldResetTimeout = currentState.shouldResetTimeout, onRetry = currentState.onRetry;
delay = retryDelay(currentState.retryCount, error); // Axios fails merging this configuration to the default configuration because it has an issue
// with circular structures: https://github.com/mzabriskie/axios/issues/370
fixConfig(axios, config);
if (!(!shouldResetTimeout && config.timeout && currentState.lastRequestTime)) {
_context.next = 17;
break;
}
lastRequestDuration = Date.now() - currentState.lastRequestTime;
timeout = config.timeout - lastRequestDuration - delay;
if (!(timeout <= 0)) {
_context.next = 16;
break;
}
return _context.abrupt("return", Promise.reject(error));
case 16:
config.timeout = timeout;
case 17:
config.transformRequest = [function (data) {
return data;
}];
_context.next = 20;
return onRetry(currentState.retryCount, error, config);
case 20:
return _context.abrupt("return", new Promise(function (resolve) {
return setTimeout(function () {
return resolve(axios(config));
}, delay);
}));
case 21:
return _context.abrupt("return", Promise.reject(error));
case 22:
case "end":
return _context.stop();
}
}
}, _callee);
}));
return function (_x3) {
return _ref.apply(this, arguments);
};
}());
return {
requestInterceptorId: requestInterceptorId,
responseInterceptorId: responseInterceptorId
};
} // Compatibility with CommonJS
axiosRetry.isNetworkError = isNetworkError;
axiosRetry.isSafeRequestError = isSafeRequestError;
axiosRetry.isIdempotentRequestError = isIdempotentRequestError;
axiosRetry.isNetworkOrIdempotentRequestError = isNetworkOrIdempotentRequestError;
axiosRetry.exponentialDelay = exponentialDelay;
axiosRetry.isRetryableError = isRetryableError;
//# sourceMappingURL=index.js.map
File diff suppressed because one or more lines are too long
+3
View File
@@ -0,0 +1,3 @@
{
"type": "commonjs"
}
+304
View File
@@ -0,0 +1,304 @@
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) { symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); } keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
import isRetryAllowed from 'is-retry-allowed';
export var namespace = 'axios-retry';
/**
* @param {Error} error
* @return {boolean}
*/
export function isNetworkError(error) {
var CODE_EXCLUDE_LIST = ['ERR_CANCELED', 'ECONNABORTED'];
return !error.response && Boolean(error.code) && // Prevents retrying cancelled requests
!CODE_EXCLUDE_LIST.includes(error.code) && // Prevents retrying timed out & cancelled requests
isRetryAllowed(error) // Prevents retrying unsafe errors
;
}
var SAFE_HTTP_METHODS = ['get', 'head', 'options'];
var IDEMPOTENT_HTTP_METHODS = SAFE_HTTP_METHODS.concat(['put', 'delete']);
/**
* @param {Error} error
* @return {boolean}
*/
export function isRetryableError(error) {
return error.code !== 'ECONNABORTED' && (!error.response || error.response.status >= 500 && error.response.status <= 599);
}
/**
* @param {Error} error
* @return {boolean}
*/
export function isSafeRequestError(error) {
if (!error.config) {
// Cannot determine if the request can be retried
return false;
}
return isRetryableError(error) && SAFE_HTTP_METHODS.indexOf(error.config.method) !== -1;
}
/**
* @param {Error} error
* @return {boolean}
*/
export function isIdempotentRequestError(error) {
if (!error.config) {
// Cannot determine if the request can be retried
return false;
}
return isRetryableError(error) && IDEMPOTENT_HTTP_METHODS.indexOf(error.config.method) !== -1;
}
/**
* @param {Error} error
* @return {boolean}
*/
export function isNetworkOrIdempotentRequestError(error) {
return isNetworkError(error) || isIdempotentRequestError(error);
}
/**
* @return {number} - delay in milliseconds, always 0
*/
function noDelay() {
return 0;
}
/**
* Set delayFactor 1000 for an exponential delay to occur on the order
* of seconds
* @param {number} [retryNumber=0]
* @param {Error} error - unused; for existing API of retryDelay callback
* @param {number} [delayFactor=100] milliseconds
* @return {number} - delay in milliseconds
*/
export function exponentialDelay() {
var retryNumber = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
var error = arguments.length > 1 ? arguments[1] : undefined;
var delayFactor = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 100;
var delay = Math.pow(2, retryNumber) * delayFactor;
var randomSum = delay * 0.2 * Math.random(); // 0-20% of the delay
return delay + randomSum;
}
/** @type {IAxiosRetryConfig} */
export var DEFAULT_OPTIONS = {
retries: 3,
retryCondition: isNetworkOrIdempotentRequestError,
retryDelay: noDelay,
shouldResetTimeout: false,
onRetry: () => {}
};
/**
* Returns the axios-retry options for the current request
* @param {AxiosRequestConfig} config
* @param {IAxiosRetryConfig} defaultOptions
* @return {IAxiosRetryConfigExtended}
*/
function getRequestOptions(config, defaultOptions) {
return _objectSpread(_objectSpread(_objectSpread({}, DEFAULT_OPTIONS), defaultOptions), config[namespace]);
}
/**
* Initializes and returns the retry state for the given request/config
* @param {AxiosRequestConfig} config
* @param {IAxiosRetryConfig} defaultOptions
* @return {IAxiosRetryConfigExtended}
*/
function getCurrentState(config, defaultOptions) {
var currentState = getRequestOptions(config, defaultOptions);
currentState.retryCount = currentState.retryCount || 0;
config[namespace] = currentState;
return currentState;
}
/**
* @param {Axios} axios
* @param {AxiosRequestConfig} config
*/
function fixConfig(axios, config) {
if (axios.defaults.agent === config.agent) {
delete config.agent;
}
if (axios.defaults.httpAgent === config.httpAgent) {
delete config.httpAgent;
}
if (axios.defaults.httpsAgent === config.httpsAgent) {
delete config.httpsAgent;
}
}
/**
* Checks retryCondition if request can be retried. Handles it's returning value or Promise.
* @param {IAxiosRetryConfigExtended} currentState
* @param {Error} error
* @return {Promise<boolean>}
*/
function shouldRetry(_x, _x2) {
return _shouldRetry.apply(this, arguments);
}
/**
* Adds response interceptors to an axios instance to retry requests failed due to network issues
*
* @example
*
* import axios from 'axios';
*
* axiosRetry(axios, { retries: 3 });
*
* axios.get('http://example.com/test') // The first request fails and the second returns 'ok'
* .then(result => {
* result.data; // 'ok'
* });
*
* // Exponential back-off retry delay between requests
* axiosRetry(axios, { retryDelay : axiosRetry.exponentialDelay});
*
* // Custom retry delay
* axiosRetry(axios, { retryDelay : (retryCount) => {
* return retryCount * 1000;
* }});
*
* // Also works with custom axios instances
* const client = axios.create({ baseURL: 'http://example.com' });
* axiosRetry(client, { retries: 3 });
*
* client.get('/test') // The first request fails and the second returns 'ok'
* .then(result => {
* result.data; // 'ok'
* });
*
* // Allows request-specific configuration
* client
* .get('/test', {
* 'axios-retry': {
* retries: 0
* }
* })
* .catch(error => { // The first request fails
* error !== undefined
* });
*
* @param {Axios} axios An axios instance (the axios object or one created from axios.create)
* @param {Object} [defaultOptions]
* @param {number} [defaultOptions.retries=3] Number of retries
* @param {boolean} [defaultOptions.shouldResetTimeout=false]
* Defines if the timeout should be reset between retries
* @param {Function} [defaultOptions.retryCondition=isNetworkOrIdempotentRequestError]
* A function to determine if the error can be retried
* @param {Function} [defaultOptions.retryDelay=noDelay]
* A function to determine the delay between retry requests
* @param {Function} [defaultOptions.onRetry=()=>{}]
* A function to get notified when a retry occurs
* @return {{ requestInterceptorId: number, responseInterceptorId: number }}
* The ids of the interceptors added to the request and to the response (so they can be ejected at a later time)
*/
function _shouldRetry() {
_shouldRetry = _asyncToGenerator(function* (currentState, error) {
var {
retries,
retryCondition
} = currentState;
var shouldRetryOrPromise = currentState.retryCount < retries && retryCondition(error); // This could be a promise
if (typeof shouldRetryOrPromise === 'object') {
try {
var shouldRetryPromiseResult = yield shouldRetryOrPromise; // keep return true unless shouldRetryPromiseResult return false for compatibility
return shouldRetryPromiseResult !== false;
} catch (_err) {
return false;
}
}
return shouldRetryOrPromise;
});
return _shouldRetry.apply(this, arguments);
}
export default function axiosRetry(axios, defaultOptions) {
var requestInterceptorId = axios.interceptors.request.use(config => {
var currentState = getCurrentState(config, defaultOptions);
currentState.lastRequestTime = Date.now();
return config;
});
var responseInterceptorId = axios.interceptors.response.use(null, /*#__PURE__*/function () {
var _ref = _asyncToGenerator(function* (error) {
var {
config
} = error; // If we have no information to retry the request
if (!config) {
return Promise.reject(error);
}
var currentState = getCurrentState(config, defaultOptions);
if (yield shouldRetry(currentState, error)) {
currentState.retryCount += 1;
var {
retryDelay,
shouldResetTimeout,
onRetry
} = currentState;
var delay = retryDelay(currentState.retryCount, error); // Axios fails merging this configuration to the default configuration because it has an issue
// with circular structures: https://github.com/mzabriskie/axios/issues/370
fixConfig(axios, config);
if (!shouldResetTimeout && config.timeout && currentState.lastRequestTime) {
var lastRequestDuration = Date.now() - currentState.lastRequestTime;
var timeout = config.timeout - lastRequestDuration - delay;
if (timeout <= 0) {
return Promise.reject(error);
}
config.timeout = timeout;
}
config.transformRequest = [data => data];
yield onRetry(currentState.retryCount, error, config);
return new Promise(resolve => setTimeout(() => resolve(axios(config)), delay));
}
return Promise.reject(error);
});
return function (_x3) {
return _ref.apply(this, arguments);
};
}());
return {
requestInterceptorId,
responseInterceptorId
};
} // Compatibility with CommonJS
axiosRetry.isNetworkError = isNetworkError;
axiosRetry.isSafeRequestError = isSafeRequestError;
axiosRetry.isIdempotentRequestError = isIdempotentRequestError;
axiosRetry.isNetworkOrIdempotentRequestError = isNetworkOrIdempotentRequestError;
axiosRetry.exponentialDelay = exponentialDelay;
axiosRetry.isRetryableError = isRetryableError;
//# sourceMappingURL=index.js.map
File diff suppressed because one or more lines are too long
+3
View File
@@ -0,0 +1,3 @@
{
"type": "module"
}
+73
View File
@@ -0,0 +1,73 @@
{
"name": "axios-retry",
"version": "3.9.1",
"author": "Rubén Norte <ruben.norte@softonic.com>",
"description": "Axios plugin that intercepts failed requests and retries them whenever posible.",
"license": "Apache-2.0",
"homepage": "https://github.com/softonic/axios-retry",
"files": [
"es",
"lib",
"index.js",
"index.d.ts"
],
"scripts": {
"lint": "eslint es/**/*.mjs spec/**/*.spec.mjs",
"pretest": "npm run lint",
"test": "NODE_OPTIONS=--es-module-specifier-resolution=node jasmine",
"prebuild": "npm run test",
"build": "rm -rf lib && babel es -d lib/esm --source-maps && babel es -d lib/cjs --config-file ./babel.config.cjs.json --source-maps && ./fixup",
"prerelease": "npm run build",
"release": "npm version -m \"New version: %s\"",
"postrelease": "npm run push && npm publish",
"push": "git push origin master && git push origin --tags",
"prepare": "husky install"
},
"lint-staged": {
"*.+(js|mjs)": [
"eslint --cache --fix",
"prettier --write"
],
"*.js": "eslint --cache --fix"
},
"dependencies": {
"@babel/runtime": "^7.15.4",
"is-retry-allowed": "^2.2.0"
},
"devDependencies": {
"@babel/cli": "^7.15.7",
"@babel/core": "^7.15.5",
"@babel/plugin-transform-runtime": "^7.15.8",
"@babel/preset-env": "^7.15.6",
"axios": "^1.2.3",
"eslint": "^7.32.0",
"eslint-config-airbnb-base": "^14.2.1",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.24.2",
"eslint-plugin-jasmine": "^4.1.2",
"eslint-plugin-prettier": "^4.0.0",
"husky": "^7.0.2",
"jasmine": "^3.9.0",
"lint-staged": "^11.2.0",
"nock": "^13.1.3",
"prettier": "^2.4.1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/softonic/axios-retry.git"
},
"bugs": {
"url": "https://github.com/softonic/axios-retry/issues"
},
"types": "./index.d.ts",
"main": "index.js",
"module": "lib/esm/index.js",
"exports": {
".": {
"types": "./index.d.ts",
"import": "./lib/esm/index.js",
"require": "./index.js"
},
"./package.json": "./package.json"
}
}