Skip to main content

Retry

Three retry surfaces, from simplest to most flexible.

The @Retry decorator (simplest)

Fixed-delay retry on a method. Three attempts by default, 1000 ms between them.

import { Retry } from '@omnitron-dev/titan/decorators';

@Retry({ attempts: 3, delay: 200 })
async fetchUpstream() {
return await fetch(this.url);
}
OptionDefaultEffect
attempts3Total attempts (including the first try)
delay1000ms between attempts

@Retry retries on any thrown error — there is no classifier in the decorator. If you need to retry only on specific errors (transient), wrap the work yourself with the retry() function described below.

The retry() function (flexible)

From @omnitron-dev/titan/utils:

import { retry, BackoffStrategy } from '@omnitron-dev/titan/utils';
import { isOperationalError } from '@omnitron-dev/titan/utils';

await retry(
() => fetch(url),
{
maxRetries: 3,
initialDelay: 100,
maxDelay: 5_000,
backoffMultiplier: 2,
jitterFactor: 0.1,
backoffStrategy: BackoffStrategy.Exponential,
shouldRetry: (error) => isOperationalError(error),
onRetry: (error, attempt, delay) => log.warn('retry', { attempt, delay }),
},
);

Real shape of RetryOptions lives in utils/resilience.ts. Common knobs (note the exact field names):

  • maxRetries — maximum retry attempts (in addition to the first try). Default 3.
  • initialDelay / maxDelay — bounds (ms). Defaults 100 / 30_000.
  • backoffMultiplier — multiplier for exponential backoff. Default 2.
  • jitterFactor0..1 random variance applied to the delay. Default 0.1.
  • backoffStrategyBackoffStrategy.Fixed | .Linear | .Exponential | .ExponentialWithJitter. Default .Exponential.
  • shouldRetry(error, attempt) — classifier predicate; default retries everything.
  • onRetry(error, attempt, delay) — callback per attempt.

The computeBackoff helper

For custom retry loops where retry() doesn't fit:

import { computeBackoff } from '@omnitron-dev/titan/utils';

let attempt = 0;
while (true) {
try {
return await fetch(url);
} catch (e) {
if (++attempt >= 3) throw e;
const delayMs = computeBackoff({
attempt, // 0-indexed
baseMs: 100,
maxMs: 5_000,
factor: 2, // exponential growth factor (not `multiplier`)
jitter: 0.25, // 0..1 fraction, additive by default
});
await sleep(delayMs);
}
}

IBackoffOptions covers exponential backoff with jitter — the same math retry() uses internally.

Retry strategies registry

utils/retry.ts exposes a richer family of named strategies:

import {
RetryStrategies,
calculateStrategyDelay,
createRetryDelayFn,
addJitter,
getFibonacciDelay,
} from '@omnitron-dev/titan/utils';

RetryStrategies is a registry of named delay functions (exponential, linear, fixed, fibonacci, custom). Use when you want to plug a strategy by name from configuration.

When NOT to retry

  • Validation failures. They will fail again on retry.
  • Auth failures (401, 403). They won't change with retry.
  • Idempotency-unsafe operations. A POST that creates a resource should not be retried unless you have an idempotency key.

The default shouldRetry: (e) => isOperationalError(e) handles the first two. The third is your responsibility — wrap idempotency- sensitive calls with an idempotency key your backend honours.

Compose with a circuit breaker

A retry without a circuit breaker can hammer a failing backend:

import { CircuitBreaker, retry, isOperationalError } from '@omnitron-dev/titan/utils';

const breaker = new CircuitBreaker({ failureThreshold: 5, resetTimeout: 60_000 });

await breaker.execute(() =>
retry(() => callBackend(), { maxRetries: 3, shouldRetry: isOperationalError }),
);

When the circuit is open, retries fail fast — they never call the backend, so no exponential pile-up.

Anti-patterns

  • Unlimited retries. Always bound with maxRetries.
  • Retry on every error. Use a shouldRetry predicate (isOperationalError is the safe default).
  • No backoff. Immediate retry is a thundering herd. Use one of the named strategies.
  • No jitter. Synchronised retries from many clients create bursts. Set a non-zero jitterFactor (e.g. 0.3), or use BackoffStrategy.ExponentialWithJitter.

→ Next: Circuit Breaker.