Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hard copied retry-axios into repository to support es5 #41

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,7 @@
"dependencies": {
"axios": "^0.18.0",
"jwt-decode": "^2.2.0",
"query-string": "^5.1.1",
"retry-axios": "^0.4.1"
"query-string": "^5.1.1"
},
"publishConfig": {
"access": "public",
Expand Down
2 changes: 1 addition & 1 deletion src/core.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright 2018 Cognite AS

import axios, { AxiosError, AxiosRequestConfig } from 'axios';
import { attach } from 'retry-axios';
import { attach } from './helpers/retry-axios';
import { MetadataMap } from './MetadataMap';

/** @hidden */
Expand Down
218 changes: 218 additions & 0 deletions src/helpers/retry-axios.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
// Code copied from https://github.com/JustinBeckwith/retry-axios because of https://github.com/cognitedata/cognitesdk-js/issues/40 and https://github.com/JustinBeckwith/retry-axios/issues/37

import axios, {
AxiosError,
AxiosInstance,
AxiosRequestConfig,
AxiosResponse,
} from 'axios';

/**
* Configuration for the Axios `request` method.
*/
export interface RetryConfig {
/**
* The number of times to retry the request. Defaults to 3.
*/
retry?: number;

/**
* The number of retries already attempted.
*/
currentRetryAttempt?: number;

/**
* The amount of time to initially delay the retry. Defaults to 100.
*/
retryDelay?: number;

/**
* The instance of the axios object to which the interceptor is attached.
*/
instance?: AxiosInstance;

/**
* The HTTP Methods that will be automatically retried.
* Defaults to ['GET','PUT','HEAD','OPTIONS','DELETE']
*/
httpMethodsToRetry?: string[];

/**
* The HTTP response status codes that will automatically be retried.
* Defaults to: [[100, 199], [429, 429], [500, 599]]
*/
statusCodesToRetry?: number[][];

/**
* Function to invoke when a retry attempt is made.
*/
onRetryAttempt?: (err: AxiosError) => void;

/**
* Function to invoke which determines if you should retry
*/
shouldRetry?: (err: AxiosError) => boolean;

/**
* When there is no response, the number of retries to attempt. Defaults to 2.
*/
noResponseRetries?: number;
}

export type RaxConfig = {
raxConfig: RetryConfig;
} & AxiosRequestConfig;

/**
* Attach the interceptor to the Axios instance.
* @param instance The optional Axios instance on which to attach the
* interceptor.
* @returns The id of the interceptor attached to the axios instance.
*/
export function attach(instance?: AxiosInstance) {
instance = instance || axios;
return instance.interceptors.response.use(onFulfilled, onError);
}

/**
* Eject the Axios interceptor that is providing retry capabilities.
* @param interceptorId The interceptorId provided in the config.
* @param instance The axios instance using this interceptor.
*/
export function detach(interceptorId: number, instance?: AxiosInstance) {
instance = instance || axios;
instance.interceptors.response.eject(interceptorId);
}

function onFulfilled(res: AxiosResponse) {
return res;
}

function onError(err: AxiosError) {
const config = (err.config as RaxConfig).raxConfig || {};
config.currentRetryAttempt = config.currentRetryAttempt || 0;
config.retry =
config.retry === undefined || config.retry === null ? 3 : config.retry;
config.retryDelay = config.retryDelay || 100;
config.instance = config.instance || axios;
config.httpMethodsToRetry = config.httpMethodsToRetry || [
'GET',
'HEAD',
'PUT',
'OPTIONS',
'DELETE',
];
config.noResponseRetries =
config.noResponseRetries === undefined || config.noResponseRetries === null
? 2
: config.noResponseRetries;

// If this wasn't in the list of status codes where we want
// to automatically retry, return.
const retryRanges = [
// https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
// 1xx - Retry (Informational, request still processing)
// 2xx - Do not retry (Success)
// 3xx - Do not retry (Redirect)
// 4xx - Do not retry (Client errors)
// 429 - Retry ("Too Many Requests")
// 5xx - Retry (Server errors)
[100, 199],
[429, 429],
[500, 599],
];
config.statusCodesToRetry = config.statusCodesToRetry || retryRanges;

// Put the config back into the err
(err.config as RaxConfig).raxConfig = config;

// Determine if we should retry the request
const shouldRetryFn = config.shouldRetry || shouldRetryRequest;
if (!shouldRetryFn(err)) {
return Promise.reject(err);
}

// Calculate time to wait with exponential backoff.
// Formula: (2^c - 1 / 2) * 1000
const delay = ((Math.pow(2, config.currentRetryAttempt) - 1) / 2) * 1000;

// We're going to retry! Incremenent the counter.
(err.config as RaxConfig).raxConfig!.currentRetryAttempt! += 1;

// Create a promise that invokes the retry after the backOffDelay
const backoff = new Promise(resolve => {
setTimeout(resolve, delay);
});

// Notify the user if they added an `onRetryAttempt` handler
if (config.onRetryAttempt) {
config.onRetryAttempt(err);
}

// Return the promise in which recalls axios to retry the request
return backoff.then(() => config.instance!.request(err.config));
}

/**
* Determine based on config if we should retry the request.
* @param err The AxiosError passed to the interceptor.
*/
function shouldRetryRequest(err: AxiosError) {
const config = (err.config as RaxConfig).raxConfig;

// If there's no config, or retries are disabled, return.
if (!config || config.retry === 0) {
return false;
}

// Check if this error has no response (ETIMEDOUT, ENOTFOUND, etc)
if (
!err.response &&
(config.currentRetryAttempt || 0) >= config.noResponseRetries!
) {
return false;
}

// Only retry with configured HttpMethods.
if (
!err.config.method ||
config.httpMethodsToRetry!.indexOf(err.config.method.toUpperCase()) < 0
) {
return false;
}

// If this wasn't in the list of status codes where we want
// to automatically retry, return.
if (err.response && err.response.status) {
let isInRange = false;
for (const [min, max] of config.statusCodesToRetry!) {
const status = err.response.status;
if (status >= min && status <= max) {
isInRange = true;
break;
}
}
if (!isInRange) {
return false;
}
}

// If we are out of retry attempts, return
config.currentRetryAttempt = config.currentRetryAttempt || 0;
if (config.currentRetryAttempt >= config.retry!) {
return false;
}

return true;
}

/**
* Acquire the raxConfig object from an AxiosError if available.
* @param err The Axios error with a config object.
*/
export function getConfig(err: AxiosError) {
if (err && err.config) {
return (err.config as RaxConfig).raxConfig;
}
return;
}
5 changes: 0 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3045,11 +3045,6 @@ ret@~0.1.10:
version "0.1.15"
resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"

retry-axios@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/retry-axios/-/retry-axios-0.4.1.tgz#22ee392c6f20ae858d06650fa06091ea4f3406e5"
integrity sha512-h3mdzDUw4MlvzjxJ96mysapyxcHzAmGVywrBtU5oAtXI2aBxMEgcmyepkKfoVXK6we2padRl1BwwvL1N7+lPwA==

rimraf@^2.5.4, rimraf@^2.6.1:
version "2.6.2"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36"
Expand Down