Skip to content

Commit

Permalink
feat(retry): support resetting attempt number
Browse files Browse the repository at this point in the history
  • Loading branch information
aikoven committed Jul 11, 2022
1 parent 85452e0 commit 6e387a1
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 9 deletions.
21 changes: 19 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -386,20 +386,37 @@ await withLock(signal, redlock, 'the-lock-key', async signal => {
```ts
function retry<T>(
signal: AbortSignal,
fn: (signal: AbortSignal, attempt: number) => Promise<T>,
fn: (signal: AbortSignal, attempt: number, reset: () => void) => Promise<T>,
options?: RetryOptions,
): Promise<T>;

type RetryOptions = {
baseMs?: number;
maxDelayMs?: number;
maxAttempts?: number;
onError?: (error: any, attempt: number, delayMs: number) => void;
onError?: (error: unknown, attempt: number, delayMs: number) => void;
};
```

Retry a function with exponential backoff.

- `fn`

A function that will be called and retried in case of error. It receives:

- `signal`

`AbortSignal` that is aborted when the signal passed to `retry` is aborted.

- `attempt`

Attempt number starting with 0.

- `reset`

Function that sets attempt number to -1 so that the next attempt will be
made without delay.

- `RetryOptions.baseMs`

Starting delay before first retry attempt in milliseconds.
Expand Down
34 changes: 27 additions & 7 deletions src/retry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,14 @@ export type RetryOptions = {

/**
* Retry function with exponential backoff.
*
* The function receives AbortSignal, attempt number starting with 0, and reset
* function that sets attempt number to -1 so that the next attempt will be
* made without delay.
*/
export async function retry<T>(
signal: AbortSignal,
fn: (signal: AbortSignal, attempt: number) => Promise<T>,
fn: (signal: AbortSignal, attempt: number, reset: () => void) => Promise<T>,
options: RetryOptions = {},
): Promise<T> {
const {
Expand All @@ -50,25 +54,41 @@ export async function retry<T>(
maxAttempts = Infinity,
} = options;

for (let attempt = 0; ; attempt++) {
let attempt = 0;

const reset = () => {
attempt = -1;
};

while (true) {
try {
return await fn(signal, attempt);
return await fn(signal, attempt, reset);
} catch (error) {
rethrowAbortError(error);

if (attempt >= maxAttempts) {
throw error;
}

// https://aws.amazon.com/ru/blogs/architecture/exponential-backoff-and-jitter/
const backoff = Math.min(maxDelayMs, Math.pow(2, attempt) * baseMs);
const delayMs = Math.round((backoff * (1 + Math.random())) / 2);
let delayMs: number;

if (attempt === -1) {
delayMs = 0;
} else {
// https://aws.amazon.com/ru/blogs/architecture/exponential-backoff-and-jitter/
const backoff = Math.min(maxDelayMs, Math.pow(2, attempt) * baseMs);
delayMs = Math.round((backoff * (1 + Math.random())) / 2);
}

if (onError) {
onError(error, attempt, delayMs);
}

await delay(signal, delayMs);
if (delayMs !== 0) {
await delay(signal, delayMs);
}

attempt += 1;
}
}
}

0 comments on commit 6e387a1

Please sign in to comment.