123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145 |
- import isRetryAllowed from 'is-retry-allowed';
- export const namespace = 'axios-retry';
- export function isNetworkError(error) {
- const CODE_EXCLUDE_LIST = ['ERR_CANCELED', 'ECONNABORTED'];
- if (error.response) {
- return false;
- }
- if (!error.code) {
- return false;
- }
- // Prevents retrying timed out & cancelled requests
- if (CODE_EXCLUDE_LIST.includes(error.code)) {
- return false;
- }
- // Prevents retrying unsafe errors
- return isRetryAllowed(error);
- }
- const SAFE_HTTP_METHODS = ['get', 'head', 'options'];
- const IDEMPOTENT_HTTP_METHODS = SAFE_HTTP_METHODS.concat(['put', 'delete']);
- export function isRetryableError(error) {
- return (error.code !== 'ECONNABORTED' &&
- (!error.response || (error.response.status >= 500 && error.response.status <= 599)));
- }
- export function isSafeRequestError(error) {
- if (!error.config?.method) {
- // Cannot determine if the request can be retried
- return false;
- }
- return isRetryableError(error) && SAFE_HTTP_METHODS.indexOf(error.config.method) !== -1;
- }
- export function isIdempotentRequestError(error) {
- if (!error.config?.method) {
- // Cannot determine if the request can be retried
- return false;
- }
- return isRetryableError(error) && IDEMPOTENT_HTTP_METHODS.indexOf(error.config.method) !== -1;
- }
- export function isNetworkOrIdempotentRequestError(error) {
- return isNetworkError(error) || isIdempotentRequestError(error);
- }
- function noDelay() {
- return 0;
- }
- export function exponentialDelay(retryNumber = 0, _error = undefined, delayFactor = 100) {
- const delay = 2 ** retryNumber * delayFactor;
- const randomSum = delay * 0.2 * Math.random(); // 0-20% of the delay
- return delay + randomSum;
- }
- export const DEFAULT_OPTIONS = {
- retries: 3,
- retryCondition: isNetworkOrIdempotentRequestError,
- retryDelay: noDelay,
- shouldResetTimeout: false,
- onRetry: () => { },
- onMaxRetryTimesExceeded: () => { }
- };
- function getRequestOptions(config, defaultOptions) {
- return { ...DEFAULT_OPTIONS, ...defaultOptions, ...config[namespace] };
- }
- function setCurrentState(config, defaultOptions) {
- const currentState = getRequestOptions(config, defaultOptions || {});
- currentState.retryCount = currentState.retryCount || 0;
- currentState.lastRequestTime = currentState.lastRequestTime || Date.now();
- config[namespace] = currentState;
- return currentState;
- }
- function fixConfig(axiosInstance, config) {
- // @ts-ignore
- if (axiosInstance.defaults.agent === config.agent) {
- // @ts-ignore
- delete config.agent;
- }
- if (axiosInstance.defaults.httpAgent === config.httpAgent) {
- delete config.httpAgent;
- }
- if (axiosInstance.defaults.httpsAgent === config.httpsAgent) {
- delete config.httpsAgent;
- }
- }
- async function shouldRetry(currentState, error) {
- const { retries, retryCondition } = currentState;
- const shouldRetryOrPromise = (currentState.retryCount || 0) < 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;
- }
- async function handleMaxRetryTimesExceeded(currentState, error) {
- if (currentState.retryCount >= currentState.retries)
- await currentState.onMaxRetryTimesExceeded(error, currentState.retryCount);
- }
- const axiosRetry = (axiosInstance, defaultOptions) => {
- const requestInterceptorId = axiosInstance.interceptors.request.use((config) => {
- setCurrentState(config, defaultOptions);
- return config;
- });
- const responseInterceptorId = axiosInstance.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 = setCurrentState(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(axiosInstance, 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(axiosInstance(config)), delay);
- });
- }
- await handleMaxRetryTimesExceeded(currentState, error);
- 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;
- export default axiosRetry;
|