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

Add retry.delay option to control the time between retries #533

Merged
merged 6 commits into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from 2 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
4 changes: 2 additions & 2 deletions source/core/Ky.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,8 +234,8 @@ export class Ky {
}
}

const BACKOFF_FACTOR = 0.3;
return Math.min(this._options.retry.backoffLimit, BACKOFF_FACTOR * (2 ** (this._retryCount - 1)) * 1000);
const retryDelay = this._options.retry.delay(this._retryCount);
return Math.min(this._options.retry.backoffLimit, retryDelay);
}

return 0;
Expand Down
2 changes: 1 addition & 1 deletion source/types/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ export interface Options extends Omit<RequestInit, 'headers'> { // eslint-disabl

If `maxRetryAfter` is set to `undefined`, it will use `options.timeout`. If [`Retry-After`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After) header is greater than `maxRetryAfter`, it will cancel the request.

Delays between retries is calculated with the function `0.3 * (2 ** (retry - 1)) * 1000`, where `retry` is the attempt number (starts from 1).
By default delays between retries are calculated with the function `0.3 * (2 ** (retry - 1)) * 1000`, where `retry` is the attempt number (starts from 1), however this can be changed by passing a `delay` function.
rclarey marked this conversation as resolved.
Show resolved Hide resolved

Retries are not triggered following a timeout.

Expand Down
7 changes: 7 additions & 0 deletions source/types/retry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,11 @@ export type RetryOptions = {
@default Infinity
*/
backoffLimit?: number;

/**
A function to calculate the delay between retries given `attemptCount` (starts from 1).

@default attemptCount => 0.3 * (2 ** (attemptCount - 1)) * 1000
*/
delay?: (attemptCount: number) => number;
};
1 change: 1 addition & 0 deletions source/utils/normalize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const defaultRetryOptions: Required<RetryOptions> = {
afterStatusCodes: retryAfterStatusCodes,
maxRetryAfter: Number.POSITIVE_INFINITY,
backoffLimit: Number.POSITIVE_INFINITY,
delay: attemptCount => 0.3 * (2 ** (attemptCount - 1)) * 1000,
};

export const normalizeRetryOptions = (retry: number | RetryOptions = {}): Required<RetryOptions> => {
Expand Down
49 changes: 46 additions & 3 deletions test/retry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ const defaultRetryCount = 2;
const retryAfterOn413 = 2;
const lastTried413access = Date.now();

// We allow the tests to take more time on CI than locally, to reduce flakiness
const allowedOffset = process.env.CI ? 1000 : 300;

test('network error', async t => {
let requestCount = 0;

Expand Down Expand Up @@ -458,9 +461,6 @@ test('respect maximum backoff', async t => {
}
});

// We allow the test to take more time on CI than locally, to reduce flakiness
const allowedOffset = process.env.CI ? 1000 : 300;

// Register observer that asserts on duration when a measurement is performed
const obs = new PerformanceObserver(items => {
const measurements = items.getEntries();
Expand Down Expand Up @@ -499,3 +499,46 @@ test('respect maximum backoff', async t => {

await server.close();
});

test('respect custom retry.delay', async t => {
const retryCount = 5;
let requestCount = 0;

const server = await createHttpTestServer();
server.get('/', (_request, response) => {
requestCount++;

if (requestCount === retryCount) {
response.end(fixture);
} else {
response.sendStatus(500);
}
});

// Register observer that asserts on duration when a measurement is performed
const obs = new PerformanceObserver(items => {
const measurements = items.getEntries();

const duration = measurements[0].duration ?? Number.NaN;
const expectedDuration = 200 + 300 + 400 + 500;

t.true(Math.abs(duration - expectedDuration) < allowedOffset, `Duration of ${duration}ms is not close to expected duration ${expectedDuration}ms`);

obs.disconnect();
});
obs.observe({entryTypes: ['measure']});

// Start measuring
performance.mark('start');
t.is(await ky(server.url, {
retry: {
limit: retryCount,
delay: n => 100 * (n + 1),
},
}).text(), fixture);
performance.mark('end');

performance.measure('linear', 'start', 'end');

await server.close();
});
sindresorhus marked this conversation as resolved.
Show resolved Hide resolved
Loading