Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions .changeset/easy-humans-unite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/pacer': minor
---

feat: add maxWait option to AsyncRetryer
55 changes: 54 additions & 1 deletion docs/guides/async-retrying.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ async function fetchData(url: string) {
const fetchWithRetry = asyncRetry(fetchData, {
maxAttempts: 3,
backoff: 'exponential',
baseWait: 1000
baseWait: 1000,
maxWait: 5000 // Cap wait time at 5 seconds
})

// Call it
Expand Down Expand Up @@ -115,6 +116,7 @@ const retryer = new AsyncRetryer(
maxAttempts: 5,
backoff: 'exponential',
baseWait: 1000,
maxWait: 5000, // Cap wait time at 5 seconds
jitter: 0.1, // Add 10% random variation
maxExecutionTime: 5000, // Abort individual calls after 5 seconds
maxTotalExecutionTime: 30000, // Abort entire operation after 30 seconds
Expand Down Expand Up @@ -171,6 +173,7 @@ const sharedOptions = asyncRetryerOptions({
maxAttempts: 3,
backoff: 'exponential',
baseWait: 1000,
maxWait: 5000, // Cap wait time at 5 seconds
onSuccess: (result, args, retryer) => console.log('Success')
})

Expand Down Expand Up @@ -230,6 +233,41 @@ const retryer = new AsyncRetryer(asyncFn, {
// Attempt 5: wait 1 second
```

## Max Wait

The `maxWait` option caps the maximum wait time between retries, preventing exponential or linear backoff from growing too large. This is particularly useful with exponential backoff, where wait times can quickly become very long:

```ts
const retryer = new AsyncRetryer(asyncFn, {
backoff: 'exponential',
baseWait: 1000,
maxWait: 5000 // Cap wait time at 5 seconds
})
// Attempt 1: immediate
// Attempt 2: wait 1 second (1000ms * 2^0) - not capped
// Attempt 3: wait 2 seconds (1000ms * 2^1) - not capped
// Attempt 4: wait 4 seconds (1000ms * 2^2) - not capped
// Attempt 5: wait 5 seconds (would be 8s, but capped at 5s)
// Attempt 6: wait 5 seconds (would be 16s, but capped at 5s)
```

Without `maxWait`, exponential backoff can result in very long delays (e.g., 64 seconds, 128 seconds) that may be impractical for your use case. Setting `maxWait` ensures retries continue at a reasonable interval even after many attempts.

The `maxWait` option also supports dynamic functions:

```ts
const retryer = new AsyncRetryer(asyncFn, {
backoff: 'exponential',
baseWait: 1000,
maxWait: (retryer) => {
// Increase max wait for critical operations
return retryer.store.state.executionCount > 10 ? 10000 : 5000
}
})
```

By default, `maxWait` is `Infinity`, meaning there's no cap on wait times.

## Jitter

Jitter adds randomness to retry delays to prevent thundering herd problems, where many clients retry at the same time and overwhelm a recovering service. The `jitter` option accepts a value between 0 and 1, representing the percentage of random variation to apply:
Expand Down Expand Up @@ -295,6 +333,7 @@ const retryer = new AsyncRetryer(asyncFn, {
maxAttempts: 5,
backoff: 'exponential',
baseWait: 1000,
maxWait: 5000, // Cap wait time between retries
maxExecutionTime: 5000, // Individual call timeout
maxTotalExecutionTime: 30000 // Overall operation timeout
})
Expand Down Expand Up @@ -459,6 +498,20 @@ const retryer = new AsyncRetryer(asyncFn, {
})
```

### Dynamic Max Wait

```ts
const retryer = new AsyncRetryer(asyncFn, {
backoff: 'exponential',
baseWait: 1000,
maxWait: (retryer) => {
// Increase max wait cap for critical operations
const errorCount = retryer.store.state.executionCount
return errorCount > 10 ? 10000 : 5000
}
})
```

### Enabling/Disabling

```ts
Expand Down
26 changes: 14 additions & 12 deletions docs/reference/classes/AsyncRetryer.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ title: AsyncRetryer

# Class: AsyncRetryer\<TFn\>

Defined in: [async-retryer.ts:288](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L288)
Defined in: [async-retryer.ts:296](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L296)

Provides robust retry functionality for asynchronous functions, supporting configurable backoff strategies,
attempt limits, timeout controls, and detailed state management. The AsyncRetryer class is designed to help you reliably
Expand All @@ -22,6 +22,8 @@ by automatically retrying them according to your chosen policy.
- `'fixed'`: Waits a constant amount of time (`baseWait`) between each attempt
- **Jitter**: Adds randomness to retry delays to prevent thundering herd problems (default: `0`).
Set to a value between 0-1 to apply that percentage of random variation to each delay.
- **Max Wait**: Caps the maximum wait time between retries (default: `Infinity`).
Useful for preventing exponential backoff from growing too large (e.g., cap at 30s even if exponential would be 64s).
- **Timeout Controls**: Set limits on execution time to prevent hanging operations:
- `maxExecutionTime`: Maximum time for a single function call (default: `Infinity`)
- `maxTotalExecutionTime`: Maximum time for the entire retry operation (default: `Infinity`)
Expand Down Expand Up @@ -62,7 +64,7 @@ Callbacks for lifecycle management:
## Usage

- Use for async operations that may fail transiently and benefit from retrying.
- Configure `maxAttempts`, `backoff`, `baseWait`, and `jitter` to control retry behavior.
- Configure `maxAttempts`, `backoff`, `baseWait`, `maxWait`, and `jitter` to control retry behavior.
- Set `maxExecutionTime` and `maxTotalExecutionTime` to prevent hanging operations.
- Use `onAbort`, `onError`, `onLastError`, `onRetry`, `onSettled`, `onSuccess`, `onExecutionTimeout`, and `onTotalExecutionTimeout` for custom side effects.
- Call `abort()` to cancel ongoing execution and pending retries.
Expand Down Expand Up @@ -113,7 +115,7 @@ The async function type to be retried.
new AsyncRetryer<TFn>(fn, initialOptions): AsyncRetryer<TFn>;
```

Defined in: [async-retryer.ts:301](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L301)
Defined in: [async-retryer.ts:309](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L309)

Creates a new AsyncRetryer instance

Expand Down Expand Up @@ -143,7 +145,7 @@ Configuration options for the retryer
fn: TFn;
```

Defined in: [async-retryer.ts:302](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L302)
Defined in: [async-retryer.ts:310](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L310)

The async function to retry

Expand All @@ -155,7 +157,7 @@ The async function to retry
key: string | undefined;
```

Defined in: [async-retryer.ts:292](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L292)
Defined in: [async-retryer.ts:300](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L300)

***

Expand All @@ -175,7 +177,7 @@ options: AsyncRetryerOptions<TFn> & Omit<Required<AsyncRetryerOptions<any>>,
| "onTotalExecutionTimeout">;
```

Defined in: [async-retryer.ts:293](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L293)
Defined in: [async-retryer.ts:301](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L301)

***

Expand All @@ -185,7 +187,7 @@ Defined in: [async-retryer.ts:293](https://github.com/TanStack/pacer/blob/main/p
readonly store: Store<Readonly<AsyncRetryerState<TFn>>>;
```

Defined in: [async-retryer.ts:289](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L289)
Defined in: [async-retryer.ts:297](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L297)

## Methods

Expand All @@ -195,7 +197,7 @@ Defined in: [async-retryer.ts:289](https://github.com/TanStack/pacer/blob/main/p
abort(reason): void;
```

Defined in: [async-retryer.ts:605](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L605)
Defined in: [async-retryer.ts:619](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L619)

Cancels the current execution and any pending retries

Expand All @@ -219,7 +221,7 @@ The reason for the abort (defaults to 'manual')
execute(...args): Promise<Awaited<ReturnType<TFn>> | undefined>;
```

Defined in: [async-retryer.ts:412](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L412)
Defined in: [async-retryer.ts:426](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L426)

Executes the function with retry logic

Expand Down Expand Up @@ -249,7 +251,7 @@ The last error if throwOnError is true and all retries fail
getAbortSignal(): AbortSignal | null;
```

Defined in: [async-retryer.ts:597](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L597)
Defined in: [async-retryer.ts:611](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L611)

Returns the current AbortSignal for the executing operation.
Use this signal in your async function to make it cancellable.
Expand Down Expand Up @@ -282,7 +284,7 @@ retryer.abort()
reset(): void;
```

Defined in: [async-retryer.ts:625](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L625)
Defined in: [async-retryer.ts:639](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L639)

Resets the retryer to its initial state

Expand All @@ -298,7 +300,7 @@ Resets the retryer to its initial state
setOptions(newOptions): void;
```

Defined in: [async-retryer.ts:328](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L328)
Defined in: [async-retryer.ts:336](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L336)

Updates the retryer options

Expand Down
2 changes: 1 addition & 1 deletion docs/reference/functions/asyncRetry.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ title: asyncRetry
function asyncRetry<TFn>(fn, initialOptions): (...args) => Promise<Awaited<ReturnType<TFn>> | undefined>;
```

Defined in: [async-retryer.ts:660](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L660)
Defined in: [async-retryer.ts:674](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L674)

Creates a retry-enabled version of an async function. This is a convenience wrapper
around the AsyncRetryer class that returns the execute method.
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/functions/asyncRetryerOptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ title: asyncRetryerOptions
function asyncRetryerOptions<TFn, TOptions>(options): TOptions;
```

Defined in: [async-retryer.ts:164](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L164)
Defined in: [async-retryer.ts:169](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L169)

Utility function for sharing common `AsyncRetryerOptions` options between different `AsyncRetryer` instances.

Expand Down
36 changes: 27 additions & 9 deletions docs/reference/interfaces/AsyncRetryerOptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,13 +169,31 @@ Infinity

***

### maxWait?

```ts
optional maxWait: number | (retryer) => number;
```

Defined in: [async-retryer.ts:112](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L112)

Maximum wait time in milliseconds to cap retry delays, or a function that returns the max wait time

#### Default

```ts
Infinity
```

***

### onAbort()?

```ts
optional onAbort: (reason, retryer) => void;
```

Defined in: [async-retryer.ts:111](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L111)
Defined in: [async-retryer.ts:116](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L116)

Callback invoked when the execution is aborted (manually or due to timeouts)

Expand All @@ -201,7 +219,7 @@ Callback invoked when the execution is aborted (manually or due to timeouts)
optional onError: (error, args, retryer) => void;
```

Defined in: [async-retryer.ts:118](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L118)
Defined in: [async-retryer.ts:123](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L123)

Callback invoked when any error occurs during execution (including retries)

Expand Down Expand Up @@ -231,7 +249,7 @@ Callback invoked when any error occurs during execution (including retries)
optional onExecutionTimeout: (retryer) => void;
```

Defined in: [async-retryer.ts:146](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L146)
Defined in: [async-retryer.ts:131](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L131)

Callback invoked when a single execution attempt times out (maxExecutionTime exceeded)

Expand All @@ -253,7 +271,7 @@ Callback invoked when a single execution attempt times out (maxExecutionTime exc
optional onLastError: (error, retryer) => void;
```

Defined in: [async-retryer.ts:126](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L126)
Defined in: [async-retryer.ts:135](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L135)

Callback invoked when the final error occurs after all retries are exhausted

Expand All @@ -279,7 +297,7 @@ Callback invoked when the final error occurs after all retries are exhausted
optional onRetry: (attempt, error, retryer) => void;
```

Defined in: [async-retryer.ts:130](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L130)
Defined in: [async-retryer.ts:139](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L139)

Callback invoked before each retry attempt

Expand Down Expand Up @@ -309,7 +327,7 @@ Callback invoked before each retry attempt
optional onSettled: (args, retryer) => void;
```

Defined in: [async-retryer.ts:134](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L134)
Defined in: [async-retryer.ts:143](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L143)

Callback invoked after execution completes (success or failure) of each attempt

Expand All @@ -335,7 +353,7 @@ Callback invoked after execution completes (success or failure) of each attempt
optional onSuccess: (result, args, retryer) => void;
```

Defined in: [async-retryer.ts:138](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L138)
Defined in: [async-retryer.ts:147](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L147)

Callback invoked when execution succeeds

Expand Down Expand Up @@ -365,7 +383,7 @@ Callback invoked when execution succeeds
optional onTotalExecutionTimeout: (retryer) => void;
```

Defined in: [async-retryer.ts:150](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L150)
Defined in: [async-retryer.ts:155](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L155)

Callback invoked when the total execution time times out (maxTotalExecutionTime exceeded)

Expand All @@ -387,7 +405,7 @@ Callback invoked when the total execution time times out (maxTotalExecutionTime
optional throwOnError: boolean | "last";
```

Defined in: [async-retryer.ts:158](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L158)
Defined in: [async-retryer.ts:163](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L163)

Controls when errors are thrown:
- 'last': Only throw the final error after all retries are exhausted
Expand Down
Loading
Loading