Skip to content

Commit

Permalink
Merge pull request #266 from yutak23/feature/add-onMaxRetryTimesExceeded
Browse files Browse the repository at this point in the history
Add onMaxRetryTimesExceeded option
  • Loading branch information
mindhells authored Mar 19, 2024
2 parents 286c985 + aeda463 commit 05744f5
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 1 deletion.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ client
| 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`. |
| 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. |

## Testing

Expand Down
115 changes: 115 additions & 0 deletions spec/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,121 @@ describe('axiosRetry(axios, { retries, onRetry })', () => {
});
});

describe('axiosRetry(axios, { onMaxRetryTimesExceeded })', () => {
const customError = new Error('CustomErrorAfterMaxRetryTimesExceeded');

afterEach(() => {
nock.cleanAll();
nock.enableNetConnect();
});

describe('when the onMaxRetryTimesExceeded is handled', () => {
it('should use onMaxRetryTimesExceeded set on request', (done) => {
const client = axios.create();
setupResponses(client, [() => nock('http://example.com').get('/test').reply(500, 'Failed!')]);
let calledCount = 0;
let finalRetryCount = 0;
const onMaxRetryTimesExceeded = (err, retryCount) => {
calledCount += 1;
finalRetryCount = retryCount;

expect(err).not.toBe(undefined);
};
axiosRetry(client, { retries: 2, onMaxRetryTimesExceeded });
client
.get('http://example.com/test')
.then(
() => done.fail(),
(error) => {
expect(calledCount).toBe(1);
expect(finalRetryCount).toBe(2);
done();
}
)
.catch(done.fail);
});

it('should reject with the custom error', (done) => {
const client = axios.create();
setupResponses(client, [() => nock('http://example.com').get('/test').reply(500, 'Failed!')]);
const onMaxRetryTimesExceeded = () => {
throw customError;
};
axiosRetry(client, {
retries: 2,
onMaxRetryTimesExceeded
});
client
.get('http://example.com/test')
.then(
() => done.fail(),
(error) => {
expect(error).toEqual(customError);
done();
}
)
.catch(done.fail);
});
});

describe('when the onMaxRetryTimesExceeded is returning a promise', () => {
it('should use onMaxRetryTimesExceeded set on request', (done) => {
const client = axios.create();
setupResponses(client, [() => nock('http://example.com').get('/test').reply(500, 'Failed!')]);
let calledCount = 0;
let finalRetryCount = 0;
const onMaxRetryTimesExceeded = (err, retryCount) =>
new Promise<void>((resolve) => {
setTimeout(() => {
calledCount += 1;
finalRetryCount = retryCount;

expect(err).not.toBe(undefined);
resolve(void 0);
}, 100);
});
axiosRetry(client, { retries: 2, onMaxRetryTimesExceeded });
client
.get('http://example.com/test')
.then(
() => done.fail(),
(error) => {
expect(calledCount).toBe(1);
expect(finalRetryCount).toBe(2);
done();
}
)
.catch(done.fail);
});

it('should reject with the custom error', (done) => {
const client = axios.create();
setupResponses(client, [() => nock('http://example.com').get('/test').reply(500, 'Failed!')]);
const onMaxRetryTimesExceeded = (err, retryCount) =>
new Promise<void>((_resolve, reject) => {
setTimeout(() => {
expect(err).not.toBe(undefined);
reject(customError);
}, 100);
});
axiosRetry(client, {
retries: 2,
onMaxRetryTimesExceeded
});
client
.get('http://example.com/test')
.then(
() => done.fail(),
(error) => {
expect(error).toEqual(customError);
done();
}
)
.catch(done.fail);
});
});
});

describe('isNetworkError(error)', () => {
it('should be true for network errors like connection refused', () => {
const connectionRefusedError = new AxiosError();
Expand Down
19 changes: 18 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ export interface IAxiosRetryConfig {
error: AxiosError,
requestConfig: AxiosRequestConfig
) => Promise<void> | void;
/**
* After all the retries are failed, this callback will be called with the last error
* before throwing the error.
*/
onMaxRetryTimesExceeded?: (error: AxiosError, retryCount: number) => Promise<void> | void;
}

export interface IAxiosRetryConfigExtended extends IAxiosRetryConfig {
Expand Down Expand Up @@ -141,7 +146,8 @@ export const DEFAULT_OPTIONS: Required<IAxiosRetryConfig> = {
retryCondition: isNetworkOrIdempotentRequestError,
retryDelay: noDelay,
shouldResetTimeout: false,
onRetry: () => {}
onRetry: () => {},
onMaxRetryTimesExceeded: () => {}
};

function getRequestOptions(
Expand Down Expand Up @@ -196,6 +202,14 @@ async function shouldRetry(
return shouldRetryOrPromise;
}

async function handleMaxRetryTimesExceeded(
currentState: Required<IAxiosRetryConfigExtended>,
error: AxiosError
) {
if (currentState.retryCount >= currentState.retries)
await currentState.onMaxRetryTimesExceeded(error, currentState.retryCount);
}

const axiosRetry: AxiosRetry = (axiosInstance, defaultOptions) => {
const requestInterceptorId = axiosInstance.interceptors.request.use((config) => {
setCurrentState(config, defaultOptions);
Expand Down Expand Up @@ -230,6 +244,9 @@ const axiosRetry: AxiosRetry = (axiosInstance, defaultOptions) => {
setTimeout(() => resolve(axiosInstance(config)), delay);
});
}

await handleMaxRetryTimesExceeded(currentState, error);

return Promise.reject(error);
});

Expand Down

0 comments on commit 05744f5

Please sign in to comment.