diff --git a/src/index.ts b/src/index.ts index 9ca573a..691c0bb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -70,6 +70,11 @@ export interface RetryConfig { * Max permitted Retry-After value (in ms) - rejects if greater. Defaults to 5 mins. */ maxRetryAfter?: number; + + /** + * Ceiling for calculated delay (in ms) - delay will not exceed this value. + */ + maxRetryDelay?: number; } export type RaxConfig = { @@ -258,6 +263,9 @@ function onError(err: AxiosError) { } else { delay = ((Math.pow(2, retrycount) - 1) / 2) * 1000; } + if (typeof config.maxRetryDelay === 'number') { + delay = Math.min(delay, config.maxRetryDelay); + } } setTimeout(resolve, delay); }); diff --git a/test/index.ts b/test/index.ts index c20b10f..e852960 100644 --- a/test/index.ts +++ b/test/index.ts @@ -588,6 +588,33 @@ describe('retry-axios', () => { await assert.rejects(axios(cfg)); assert.strictEqual(scopes[1].isDone(), false); }); + + it('should use maxRetryDelay', async function () { + this.timeout(1000); // Short timeout to trip test if delay longer than expected + const scopes = [ + nock(url).get('/').reply(429, undefined), + nock(url).get('/').reply(200, 'toast'), + ]; + interceptorId = rax.attach(); + const {promise, resolve} = invertedPromise(); + const clock = sinon.useFakeTimers({ + shouldAdvanceTime: true, // Otherwise interferes with nock + }); + const axiosPromise = axios({ + url, + raxConfig: { + onRetryAttempt: resolve, + retryDelay: 10000, // Higher default to ensure maxRetryDelay is used + maxRetryDelay: 5000, + backoffType: 'exponential', + }, + }); + await promise; + clock.tick(5000); // Advance clock by expected retry delay + const res = await axiosPromise; + assert.strictEqual(res.data, 'toast'); + scopes.forEach(s => s.done()); + }); }); function invertedPromise() {