Skip to content

Commit

Permalink
Merge pull request #285 from yutak23/feature/add-linearDelay
Browse files Browse the repository at this point in the history
feat: add linearDelay for retryDelay option
  • Loading branch information
mindhells authored Aug 2, 2024
2 parents 91bd08b + 756dd7b commit ef00967
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 2 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ axios.get('http://example.com/test') // The first request fails and the second r
// Exponential back-off retry delay between requests
axiosRetry(axios, { retryDelay: axiosRetry.exponentialDelay });

// Liner retry delay between requests
axiosRetry(axios, { retryDelay: axiosRetry.linearDelay() });

// Custom retry delay
axiosRetry(axios, { retryDelay: (retryCount) => {
return retryCount * 1000;
Expand Down Expand Up @@ -64,7 +67,7 @@ client
| 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`. |
| 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)) or `linearDelay`. 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`. |
| onMaxRetryTimesExceeded | `Function` | `function onMaxRetryTimesExceeded(error, retryCount) { return; }` | After all the retries are failed, this callback will be called with the last error before throwing the error. |
| validateResponse | `Function \| null` | `null` | A callback to define whether a response should be resolved or rejected. If null is passed, it will fallback to the axios default (only 2xx status codes are resolved). |
Expand Down
77 changes: 76 additions & 1 deletion spec/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import axiosRetry, {
exponentialDelay,
retryAfter,
isRetryableError,
namespace
namespace,
linearDelay
} from '../src/index';

const NETWORK_ERROR = new AxiosError('Some connection error');
Expand Down Expand Up @@ -614,6 +615,53 @@ describe('axiosRetry(axios, { retries, retryDelay })', () => {
}, done.fail);
});
});

describe('when linearDelay is supplied', () => {
it('should take more than 600 milliseconds with default delay and 4 retries', (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').replyWithError(NETWORK_ERROR),
() => nock('http://example.com').get('/test').reply(200, 'It worked!')
]);
const timeStart = new Date().getTime();
axiosRetry(client, {
retries: 4,
retryCondition: () => true,
retryDelay: linearDelay()
});
client.get('http://example.com/test').then(() => {
// 100 + 200 + 300 = 600
expect(new Date().getTime() - timeStart).toBeGreaterThanOrEqual(600);
expect(new Date().getTime() - timeStart).toBeLessThan(700);

done();
}, done.fail);
});

it('should take more than 300 milliseconds with 50ms delay and 4 retries', (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').replyWithError(NETWORK_ERROR),
() => nock('http://example.com').get('/test').reply(200, 'It worked!')
]);
const timeStart = new Date().getTime();
axiosRetry(client, {
retries: 4,
retryCondition: () => true,
retryDelay: linearDelay(50)
});
client.get('http://example.com/test').then(() => {
// 50 + 100 + 150 = 300
expect(new Date().getTime() - timeStart).toBeGreaterThanOrEqual(300);
expect(new Date().getTime() - timeStart).toBeLessThan(400);
done();
}, done.fail);
});
});
});

describe('axiosRetry(axios, { retries, onRetry })', () => {
Expand Down Expand Up @@ -1054,6 +1102,33 @@ describe('exponentialDelay', () => {
});
});

describe('linearDelay', () => {
it('should return liner retry delay', () => {
const linearDelayFunc = linearDelay();

function assertTime(retryNumber) {
const time = linearDelayFunc(retryNumber, undefined);

expect(time).toBe(100 * retryNumber);
}

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].forEach(assertTime);
});

it('should change delay time when specifying delay factor', () => {
const delayFactor = 300;
const linearDelayFunc = linearDelay(delayFactor);

function assertTime(retryNumber) {
const time = linearDelayFunc(retryNumber, undefined);

expect(time).toBe(delayFactor * retryNumber);
}

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].forEach(assertTime);
});
});

describe('retryAfter', () => {
it('should understand a numeric Retry-After header', () => {
const errorResponse = new AxiosError('Error response');
Expand Down
16 changes: 16 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export interface AxiosRetry {
isIdempotentRequestError(error: AxiosError): boolean;
isNetworkOrIdempotentRequestError(error: AxiosError): boolean;
exponentialDelay(retryNumber?: number, error?: AxiosError, delayFactor?: number): number;
linearDelay(delayFactor?: number): (retryNumber: number, error: AxiosError | undefined) => number;
}

declare module 'axios' {
Expand Down Expand Up @@ -169,6 +170,20 @@ export function exponentialDelay(
return delay + randomSum;
}

/**
* Linear delay
* @param {number | undefined} delayFactor - delay factor in milliseconds (default: 100)
* @returns {function} (retryNumber: number, error: AxiosError | undefined) => number
*/
export function linearDelay(
delayFactor: number | undefined = 100
): (retryNumber: number, error: AxiosError | undefined) => number {
return (retryNumber = 0, error = undefined) => {
const delay = retryNumber * delayFactor;
return Math.max(delay, retryAfter(error));
};
}

export const DEFAULT_OPTIONS: Required<IAxiosRetryConfig> = {
retries: 3,
retryCondition: isNetworkOrIdempotentRequestError,
Expand Down Expand Up @@ -322,5 +337,6 @@ axiosRetry.isSafeRequestError = isSafeRequestError;
axiosRetry.isIdempotentRequestError = isIdempotentRequestError;
axiosRetry.isNetworkOrIdempotentRequestError = isNetworkOrIdempotentRequestError;
axiosRetry.exponentialDelay = exponentialDelay;
axiosRetry.linearDelay = linearDelay;
axiosRetry.isRetryableError = isRetryableError;
export default axiosRetry;

0 comments on commit ef00967

Please sign in to comment.