Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

retry-pro

Package Overview
Dependencies
Maintainers
1
Versions
3
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

retry-pro

A production-grade, policy-driven TypeScript retry package.

latest
npmnpm
Version
1.2.0
Version published
Maintainers
1
Created
Source

retry-pro

A production-grade, policy-driven TypeScript retry library.

NPM Version License: MIT Tests

Overview

retry-pro provides a structured, composable approach to retrying failed async operations. Instead of re-implementing retry logic per call site, it gives you a Policy-First model that is predictable, cancellable, observability-ready, and safe for production at scale.

Key properties:

  • Zero runtime dependencies
  • Full TypeScript types with strict mode
  • Works in Node.js and browsers (separate browser bundle)
  • Tree-shakable ESM + CJS dual output

Installation

npm install retry-pro

Quick Start

import { retry } from 'retry-pro';

const result = await retry(async (signal) => {
  return await fetch('/api/data', { signal }).then(r => r.json());
}, { retries: 3 });

Always forward the signal argument into your async operation. This enables clean cancellation and hedging.

Built-in Policies

Policies bundle a retry count, delay strategy, and retry condition together. Pass a policy name instead of configuring everything manually.

PolicyRetriesStrategyRetries On
httpSafe3Exponential (1000ms base)5xx only
networkOnly5Exponential (500ms base)Network errors (ECONNRESET, ETIMEDOUT, etc.)
aggressive10Fixed (100ms)Everything
safe2Exponential (2000ms base)5xx + network errors
const data = await retry(fetchMyData, { policy: 'httpSafe' });

Register Custom Policies

import { registerPolicy, exponentialStrategy, retry } from 'retry-pro';

registerPolicy('databaseFailover', {
  retries: 3,
  strategy: exponentialStrategy(500, 2, 10000), // base 500ms, factor 2, max 10s
  shouldRetry: (err) => err.code === 'ECONNRESET'
});

await retry(queryDb, { policy: 'databaseFailover' });

Attempting to register a policy name that already exists will throw an error.

Delay Strategies

Strategies control how long to wait between attempts. All strategies cap at maxDelay (default: 30,000ms).

Exponential

import { exponentialStrategy } from 'retry-pro';
// delay = base * (factor ^ attempt), capped at maxDelay
exponentialStrategy(baseDelay: number, factor?: number, maxDelay?: number)
// e.g. exponentialStrategy(1000) → 1000, 2000, 4000, 8000...

Linear

import { linearStrategy } from 'retry-pro';
// delay = base + (increment * attempt)
linearStrategy(baseDelay: number, increment: number, maxDelay?: number)
// e.g. linearStrategy(200, 100) → 200, 300, 400...

Fixed

import { fixedStrategy } from 'retry-pro';
// delay = constant
fixedStrategy(delay: number, maxDelay?: number)
// e.g. fixedStrategy(500) → 500, 500, 500...

With Jitter

Wraps any strategy to add randomized variance, preventing thundering herd problems.

import { withJitter, exponentialStrategy } from 'retry-pro';
// delay = baseDelay + (baseDelay * jitterFactor * random)
withJitter(strategy, jitterFactor?: number, maxDelay?: number)
// e.g. withJitter(exponentialStrategy(1000), 0.2)

Core Options Reference

await retry(fn, {
  retries: 3,                    // Max retry attempts
  policy: 'httpSafe',            // Built-in or custom policy name
  strategy: fixedStrategy(500),  // Delay strategy (overrides policy's)
  shouldRetry: (err) => true,    // Condition to retry (overrides policy's)
  signal: controller.signal,     // AbortSignal for cancellation
  attemptTimeout: 5000,          // Hard timeout per attempt (ms)
  fallback: () => defaultValue,  // Value or async fn when all retries fail
  adaptive: true,                // Adaptive delay based on error type
  priority: 'high',              // 'low' | 'normal' | 'high'
  onRetry: async (ctx) => {},    // Hook before each retry
  onSuccess: async (ctx) => {},  // Hook on success
  onFailure: async (ctx) => {},  // Hook on final failure
});

Lifecycle Hooks

All hooks receive a rich context object.

await retry(fn, {
  retries: 3,
  onRetry: async ({ attempt, strategyDelayMs, totalElapsedMs, error }) => {
    console.warn(`Attempt ${attempt} failed. Retrying in ${strategyDelayMs}ms. Elapsed: ${totalElapsedMs}ms`);
  },
  onSuccess: async ({ attempt, totalElapsedMs, result }) => {
    console.log(`Succeeded on attempt ${attempt}`);
  },
  onFailure: async ({ attempt, totalElapsedMs, error }) => {
    console.error(`All retries exhausted after ${attempt} attempts`, error);
  }
});

Cancellation (AbortSignal)

Pass an AbortSignal to cancel the entire retry loop, including any active delay intervals.

const controller = new AbortController();

// Cancel after 10 seconds
setTimeout(() => controller.abort(), 10000);

try {
  await retry(fn, { signal: controller.signal, retries: 10 });
} catch (err) {
  if (err instanceof AbortError) {
    console.log('Operation was cancelled');
  }
}

Fallbacks & Attempt Timeouts

const result = await retry(fetchPrices, {
  retries: 2,
  attemptTimeout: 5000,                          // Reject any attempt taking > 5s
  fallback: () => ({ cached: true, price: 50 }) // Returned after all retries fail
});

Adaptive Delay

When adaptive: true, the delay strategy is determined automatically by inspecting the error:

Error TypeStrategy
Rate limited (429)Slow exponential (1000 * 3^attempt) or uses Retry-After header
Network error (ECONNRESET, ETIMEDOUT, etc.)Fast incremental (100 * (attempt + 1))
Everything elseStandard exponential (1000 * 2^attempt)
await retry(apiCall, { adaptive: true });

Circuit Breaker

Prevents cascading failures by stopping requests to a known-failing service. Uses a rolling failure window.

import { retry, CircuitBreaker } from 'retry-pro';

const circuit = new CircuitBreaker({
  failureThreshold: 3,   // Open after 3 failures
  resetTimeout: 30000,   // Try again after 30s (HALF_OPEN)
  successThreshold: 2,   // Require 2 successes to fully close
  window: 60000          // Only count failures within last 60s
});

await retry(fetchUsers, { circuitBreaker: circuit });

States: CLOSEDOPENHALF_OPENCLOSED

  • The circuit only opens on final failures (after all retries), not intermediate ones.
  • You can also pass raw CircuitBreakerOptions and an instance will be created for you.
await retry(fn, {
  circuitBreaker: { failureThreshold: 5, resetTimeout: 30000 }
});

Retry Budget

Prevents retry storms by capping the total retries allowed across multiple calls in a rolling time window.

import { retry, RetryBudget } from 'retry-pro';

const budget = new RetryBudget({ maxRetries: 50, window: 60000 });

// All retry() calls sharing this budget instance will collectively
// be limited to 50 retries per 60 seconds.
await retry(queryCluster, { retryBudget: budget });

Methods available on RetryBudget:

budget.canRetry()    // → boolean
budget.consume()     // Atomically checks and decrements; returns boolean
budget.getStats()    // → { remaining, resetIn, used }
budget.reset()       // Manually reset the window

You can also supply options directly and an instance will be created internally:

await retry(fn, { retryBudget: { maxRetries: 50, window: 60000 } });

Request Hedging

Launches a duplicate request if the original hasn't responded within a threshold, resolving with whichever completes first. Losers are automatically aborted.

await retry(async (signal) => fetch('/data', { signal }), {
  hedging: {
    enabled: true,
    delay: 200,          // Launch a hedge after 200ms
    maxHedges: 1,        // Max number of duplicate requests (default: 1)
    concurrencyLimit: 20 // Global limit on simultaneous hedge requests
  }
});

⚠️ Only use hedging on idempotent operations. It increases load on downstream services.

Priority System

Automatically tunes retry count and delays without manual configuration.

PriorityRetriesDelay
high+2×0.5 (50% faster)
normalunchangedunchanged
low−1 (min 0)×1.5 (50% slower)
await retry(criticalAction, { priority: 'high' });
await retry(backgroundTask, { priority: 'low' });

Error Types

import { AbortError, RetryError, CircuitOpenError } from 'retry-pro';

try {
  await retry(fn, { retries: 3 });
} catch (err) {
  if (err instanceof AbortError) { /* cancelled */ }
  if (err instanceof RetryError) {
    console.log(err.attempts);   // How many attempts were made
    console.log(err.lastError);  // The underlying error
  }
  if (err instanceof CircuitOpenError) { /* circuit is open */ }
}

OpenTelemetry Integration

Import from the retry-pro/otel sub-path to avoid bundling OTEL into your main bundle.

import { withTracing } from 'retry-pro/otel';

const data = await withTracing(fetchDatabase, retryOptions, yourOtelTracer);

Emits span events on each retry attempt and sets the span status to OK or ERROR on completion.

Production Example

import { retry, CircuitBreaker, RetryBudget } from 'retry-pro';

const circuit = new CircuitBreaker({ failureThreshold: 5, resetTimeout: 30000 });
const budget = new RetryBudget({ maxRetries: 50, window: 60000 });

try {
  const data = await retry(async (signal) => axios.get('/api', { signal }), {
    policy: 'httpSafe',
    priority: 'high',
    attemptTimeout: 5000,
    adaptive: true,
    hedging: { enabled: true, delay: 250 },
    circuitBreaker: circuit,
    retryBudget: budget,
    onRetry: ({ attempt, strategyDelayMs }) => {
      logger.warn(`Retry ${attempt}, waiting ${strategyDelayMs}ms`);
    }
  });
} catch (err) {
  if (err instanceof CircuitOpenError) {
    // Serve from cache or degrade gracefully
  }
}

Safety: Idempotency

⚠️ Only retry idempotent operations. If a POST request creates a record but times out before returning a response, retrying it will create a duplicate. Use retry only for safe operations (reads, or writes protected by idempotency keys).

License

MIT

Keywords

retry-pro

FAQs

Package last updated on 07 Apr 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts