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

Implemented per-request configuration #12

Merged
merged 1 commit into from
Jun 15, 2017
Merged
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
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,25 @@ axios.get('http://example.com/test') // The first request fails and the second r
result.data; // 'ok'
});

// Also works with custom axios instances
// 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
});
```

## Options
Expand Down
93 changes: 74 additions & 19 deletions es/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,53 @@
import isRetryAllowed from 'is-retry-allowed';
import pkg from '../package.json';

const namespace = pkg.name;

/**
* @param {Error} error
* @return {boolean}
*/
export function isNetworkError(error) {
return !error.response;
}

/**
* Initializes and returns the retry state for the given request/config
* @param {AxiosRequestConfig} config
* @return {Object}
*/
function getCurrentState(config) {
const currentState = config[namespace] || {};
currentState.retryCount = currentState.retryCount || 0;
config[namespace] = currentState;
return currentState;
}

/**
* Returns the axios-retry options for the current request
* @param {AxiosRequestConfig} config
* @param {AxiosRetryConfig} defaultOptions
* @return {AxiosRetryConfig}
*/
function getRequestOptions(config, defaultOptions) {
return Object.assign({}, defaultOptions, config[namespace]);
}

/**
* @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;
}
}

/**
* Adds response interceptors to an axios instance to retry requests failed due to network issues
Expand All @@ -23,14 +72,23 @@ import isRetryAllowed from 'is-retry-allowed';
* 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} [options]
* @param {number} [options.retries=3] Number of retries
* @param {Object} [defaultOptions]
* @param {number} [defaultOptions.retries=3] Number of retries
* @param {number} [defaultOptions.retryCondition=isNetworkError] Number of retries
*/
export default function axiosRetry(axios, {
retries = 3,
retryCondition = error => !error.response
} = {}) {
export default function axiosRetry(axios, defaultOptions) {
axios.interceptors.response.use(null, error => {
const config = error.config;

Expand All @@ -39,27 +97,24 @@ export default function axiosRetry(axios, {
return Promise.reject(error);
}

config.retryCount = config.retryCount || 0;
const {
retries = 3,
retryCondition = isNetworkError
} = getRequestOptions(config, defaultOptions);

const currentState = getCurrentState(config);

const shouldRetry = retryCondition(error)
&& error.code !== 'ECONNABORTED'
&& config.retryCount < retries
&& currentState.retryCount < retries
&& isRetryAllowed(error);

if (shouldRetry) {
config.retryCount++;
currentState.retryCount++;

// Axios fails merging this configuration to the default configuration because it has an issue
// with circular structures
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;
}
// with circular structures: https://github.com/mzabriskie/axios/issues/370
fixConfig(axios, config);

return axios(config);
}
Expand Down
30 changes: 22 additions & 8 deletions spec/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ function setupResponses(client, responses) {
configureResponse();
}

describe('axiosRetry(axios, { retries })', () => {
describe('axiosRetry(axios, { retries, retryCondition })', () => {
afterEach(() => {
nock.cleanAll();
nock.enableNetConnect();
Expand Down Expand Up @@ -207,13 +207,6 @@ describe('axiosRetry(axios, { retries })', () => {
done();
});
});
});

describe('axiosRetry(axios, { retries, shouldRetry })', () => {
afterEach(() => {
nock.cleanAll();
nock.enableNetConnect();
});

it('allows a custom retryCondition function to determine if it should retry or not', done => {
const client = axios.create();
Expand All @@ -234,4 +227,25 @@ describe('axiosRetry(axios, { retries, shouldRetry })', () => {
done();
});
});

it('should use request-specific configuration', done => {
const client = axios.create();

setupResponses(client, [
() => nock('http://example.com').get('/test').replyWithError(NETWORK_ERROR),
() => nock('http://example.com').get('/test').replyWithError(NETWORK_ERROR),
() => nock('http://example.com').get('/test').reply(200)
]);

axiosRetry(client, { retries: 0 });

client.get('http://example.com/test', {
'axios-retry': {
retries: 2
}
}).then(result => {
expect(result.status).toBe(200);
done();
}, done.fail);
});
});