Custom Policies
Create custom retry policies by extending BaseRetryPolicy or implementing the retry types directly.
Create a custom policy when the built-in FixedDelay and ExponentialBackoff policies do not match your retry rules.
Extend BaseRetryPolicy
Extending BaseRetryPolicy is the recommended path for most custom policies.
You implement next(...) only; the base class provides onExhausted with a default
RetryError return, keeps the shared run(...) orchestration, and lets you override
onExhausted when needed.
import { BaseRetryPolicy } from "@zap-studio/retry";
import type { RetryDecision, RetryDecisionInput } from "@zap-studio/retry/types";
class LinearBackoff extends BaseRetryPolicy {
constructor(
private readonly maxAttempts: number,
private readonly stepMs: number,
) {
super();
}
public next(input: RetryDecisionInput): RetryDecision {
if (input.attempt >= this.maxAttempts) {
return {
shouldRetry: false,
delayMs: 0,
reason: "max-attempts-reached",
};
}
return {
shouldRetry: true,
delayMs: input.attempt * this.stepMs,
reason: "retry",
};
}
}
const policy = new LinearBackoff(5, 250);
const value = await policy.run(async () => {
return await doWork();
});Customize Exhaustion
Override onExhausted(...) when callers need a custom terminal error.
import { BaseRetryPolicy } from "@zap-studio/retry";
import { RetryError } from "@zap-studio/retry/error";
import type { RetryExhaustedInput } from "@zap-studio/retry/types";
class UpstreamRetryError extends RetryError {
constructor(
public readonly attempts: number,
public readonly cause: unknown,
) {
super("Upstream retries exhausted.", {
attempts,
lastError: cause,
});
}
}
class UpstreamPolicy extends BaseRetryPolicy {
public next(input) {
return {
shouldRetry: input.attempt < 3,
delayMs: 100,
reason: input.attempt < 3 ? "retry" : "max-attempts-reached",
};
}
public onExhausted(input: RetryExhaustedInput): UpstreamRetryError {
return new UpstreamRetryError(input.attempts, input.error);
}
}run(...) will throw the value returned by onExhausted(...), unless throwOnExhausted is false.
Implement The Types Directly
Use the lower-level RetryPolicy type when you need to build your own orchestration layer instead of using BaseRetryPolicy.run(...).
import type { RetryPolicy } from "@zap-studio/retry/types";
import { RetryError } from "@zap-studio/retry/error";
const policy: RetryPolicy = {
next: ({ attempt }) => ({
shouldRetry: attempt < 3,
delayMs: 100,
reason: attempt < 3 ? "retry" : "max-attempts-reached",
}),
onExhausted: ({ attempts, error }) =>
new RetryError("Retry policy exhausted all attempts.", {
attempts,
lastError: error,
}),
};When you implement RetryPolicy directly, you are responsible for the retry loop, delay behavior, and how execution errors are captured.
Guidelines
- Keep
next(...)deterministic and side-effect light. - Return
shouldRetry: falsewhen the current attempt should be terminal. - Use
reasonvalues for debugging and logs. - Prefer extending
BaseRetryPolicyunless you need full orchestration control.
Last updated on