Skip to content

Commit 6e387a1

Browse files
committed
feat(retry): support resetting attempt number
1 parent 85452e0 commit 6e387a1

File tree

2 files changed

+46
-9
lines changed

2 files changed

+46
-9
lines changed

README.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -386,20 +386,37 @@ await withLock(signal, redlock, 'the-lock-key', async signal => {
386386
```ts
387387
function retry<T>(
388388
signal: AbortSignal,
389-
fn: (signal: AbortSignal, attempt: number) => Promise<T>,
389+
fn: (signal: AbortSignal, attempt: number, reset: () => void) => Promise<T>,
390390
options?: RetryOptions,
391391
): Promise<T>;
392392

393393
type RetryOptions = {
394394
baseMs?: number;
395395
maxDelayMs?: number;
396396
maxAttempts?: number;
397-
onError?: (error: any, attempt: number, delayMs: number) => void;
397+
onError?: (error: unknown, attempt: number, delayMs: number) => void;
398398
};
399399
```
400400

401401
Retry a function with exponential backoff.
402402

403+
- `fn`
404+
405+
A function that will be called and retried in case of error. It receives:
406+
407+
- `signal`
408+
409+
`AbortSignal` that is aborted when the signal passed to `retry` is aborted.
410+
411+
- `attempt`
412+
413+
Attempt number starting with 0.
414+
415+
- `reset`
416+
417+
Function that sets attempt number to -1 so that the next attempt will be
418+
made without delay.
419+
403420
- `RetryOptions.baseMs`
404421

405422
Starting delay before first retry attempt in milliseconds.

src/retry.ts

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,14 @@ export type RetryOptions = {
3737

3838
/**
3939
* Retry function with exponential backoff.
40+
*
41+
* The function receives AbortSignal, attempt number starting with 0, and reset
42+
* function that sets attempt number to -1 so that the next attempt will be
43+
* made without delay.
4044
*/
4145
export async function retry<T>(
4246
signal: AbortSignal,
43-
fn: (signal: AbortSignal, attempt: number) => Promise<T>,
47+
fn: (signal: AbortSignal, attempt: number, reset: () => void) => Promise<T>,
4448
options: RetryOptions = {},
4549
): Promise<T> {
4650
const {
@@ -50,25 +54,41 @@ export async function retry<T>(
5054
maxAttempts = Infinity,
5155
} = options;
5256

53-
for (let attempt = 0; ; attempt++) {
57+
let attempt = 0;
58+
59+
const reset = () => {
60+
attempt = -1;
61+
};
62+
63+
while (true) {
5464
try {
55-
return await fn(signal, attempt);
65+
return await fn(signal, attempt, reset);
5666
} catch (error) {
5767
rethrowAbortError(error);
5868

5969
if (attempt >= maxAttempts) {
6070
throw error;
6171
}
6272

63-
// https://aws.amazon.com/ru/blogs/architecture/exponential-backoff-and-jitter/
64-
const backoff = Math.min(maxDelayMs, Math.pow(2, attempt) * baseMs);
65-
const delayMs = Math.round((backoff * (1 + Math.random())) / 2);
73+
let delayMs: number;
74+
75+
if (attempt === -1) {
76+
delayMs = 0;
77+
} else {
78+
// https://aws.amazon.com/ru/blogs/architecture/exponential-backoff-and-jitter/
79+
const backoff = Math.min(maxDelayMs, Math.pow(2, attempt) * baseMs);
80+
delayMs = Math.round((backoff * (1 + Math.random())) / 2);
81+
}
6682

6783
if (onError) {
6884
onError(error, attempt, delayMs);
6985
}
7086

71-
await delay(signal, delayMs);
87+
if (delayMs !== 0) {
88+
await delay(signal, delayMs);
89+
}
90+
91+
attempt += 1;
7292
}
7393
}
7494
}

0 commit comments

Comments
 (0)