npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

smart-retry

v1.0.6

Published

Smart utility to retry flaky sync/async operations with delay, backoff, and retry hooks.

Readme

Smart-Retry

Smart utility to retry flaky sync/async operations with delay, backoff, jitter, abort, and retry hooks.

License: MIT

✨ Features

  • ✅ Works with sync and async functions
  • 🔁 Configurable number of retries and delay
  • ⏱️ Exponential backoff and custom schedules
  • 🎲 Jitter and randomized backoff strategies
  • 🛑 Multiple cancellation options (AbortSignal, predicate, token)
  • 🔍 onRetry hook for logging or metrics
  • ⚠️ Optional error filtering with shouldRetry
  • 🧩 Retry until condition or with custom delay logic
  • 🪶 Zero dependencies

📦 Install

npm install smart-retry

⚡ Quick Usage

import { retry } from "smart-retry";

async function fetchData() {
  const res = await fetch("https://example.com/api");
  if (!res.ok) throw new Error("API error"); // by default, all errors are retryable
  return res.json();
}

const result = await retry(() => fetchData(), {
  retries: 3,
  delay: 1000,
  factor: 2,
  onRetry: (err, attempt) => {
    console.warn(`Attempt #${attempt} failed: ${err}`);
  },
});

🔄 How Retry Works

The retry mechanism in this library follows a simple but powerful pattern:

  1. Execution: The library attempts to run your function (sync or async).
  2. Error Handling:
    • If the function succeeds (returns a value without throwing), the result is returned immediately.
    • If the function throws an error:
      • The library checks if another retry should be attempted based on the retries count and shouldRetry predicate.
      • If no more retries are allowed or shouldRetry returns false, the error is thrown.
  3. Notification: If a retry is needed, the onRetry callback is invoked with the error and attempt number.
  4. Delay: The library waits for a specified delay duration before the next attempt.
    • For regular retry: The delay increases exponentially by the specified factor.
    • For jitter: Random variation is added to prevent "thundering herd" problems.
    • For scheduled retries: The delay follows a pre-defined array of values.
  5. Cancellation: Some retry functions support cancellation via:
    • AbortSignal: Standard web API for cancellation
    • Predicate function: Custom function that returns true when retries should stop
    • Cancellation token: Object with a boolean property to cancel retries

All retry functions can handle both synchronous and asynchronous target functions, automatically wrapping results in promises for a consistent async interface.


⚙️ API Overview

Common Options

| Option | Type | Default | Description | | ------------- | ------------------------ | -------- | ----------------------------------------- | | retries | number | 3 | Number of retry attempts | | delay | number (ms) | 500 | Initial delay between retries | | factor | number | 2 | Multiplier for exponential backoff | | onRetry | (err, attempt) => void | - | Hook called on every failed retry | | shouldRetry | (err) => boolean | always | Predicate to decide if error is retryable |

Provided Functions

  • retry: Standard exponential backoff retry.
  • retryWithJitter: Adds random jitter to delay.
  • retryWithTimeout: Retries until a total timeout is reached.
  • retryWithSchedule: Retries using a custom array of delays.
  • retryWithAbortSignal: Supports aborting with an AbortSignal.
  • retryWithPredicate: Stops retrying when a predicate function returns true.
  • retryWithCancellationToken: Stops retrying when a token object's cancelled property is true.
  • retryWithMaxTotalAttempts: Specify max total attempts (not just retries).
  • retryWithPredicateDelay: Custom delay logic per attempt.
  • retryWithRandomizedBackoff: Decorrelated jitter/randomized backoff.
  • retryUntilCondition: Retries until a user-provided predicate returns true.
  • retryWithCustomExecution: Uses custom execution logic instead of try/catch to evaluate success.

🧑‍💻 Examples

Retry a Synchronous Function

import { retry } from "smart-retry";

let count = 0;
const result = await retry(() => {
  count++;
  if (count < 3) throw new Error("Oops");
  return "done";
});

console.log(result); // Output: 'done' after 3 attempts

Retry Until Condition

import { retryUntilCondition } from "smart-retry";

// Example 1: Wait for a value to reach a threshold
let value = 0;
await retryUntilCondition(
  () => ++value,
  (result) => result === 5,
  { retries: 10, delay: 100 },
);

// Example 2: Poll an API until a resource is ready
async function checkStatus() {
  const res = await fetch("https://api.example.com/status");
  const data = await res.json();
  return data.status;
}

await retryUntilCondition(
  () => checkStatus(),
  (status) => status === "ready",
  { retries: 20, delay: 500, onRetry: (err, attempt) => console.log(`Attempt ${attempt}: not ready yet`) },
);

Retry an Async Function with Exponential Backoff and Error Filtering

import { retry } from "smart-retry";

await retry(() => fetchSomething(), {
  retries: 5,
  delay: 500,
  factor: 1.5,
  shouldRetry: (err) => err instanceof TimeoutError,
});

Retry with Jitter

import { retryWithJitter } from "smart-retry";

await retryWithJitter(() => fetchSomething(), {
  retries: 4,
  delay: 200,
  jitter: 100,
  onRetry: (err, attempt) => {
    console.log(`Retry #${attempt} failed: ${err}`);
  },
});

Retry with AbortSignal

import { retryWithAbortSignal } from "smart-retry";

const controller = new AbortController();
setTimeout(() => controller.abort(), 1000);

try {
  await retryWithAbortSignal(() => fetchSomething(), {
    retries: 10,
    delay: 200,
    signal: controller.signal,
  });
} catch (err) {
  if (controller.signal.aborted) {
    console.log("Retry aborted by user.");
  } else {
    console.error("Retry failed:", err);
  }
}

Retry with Predicate

import { retryWithPredicate } from "smart-retry";

// Flag to stop retrying when set to true
let stopRetrying = false;

// Set up a separate timeout that will stop retries after 1 second
setTimeout(() => {
  stopRetrying = true;
}, 1000);

try {
  await retryWithPredicate(
    () => fetchFromSlowAPI(),
    () => stopRetrying, // Stop when our flag becomes true
    { retries: 10, delay: 200 },
  );
} catch (err) {
  if (stopRetrying) {
    console.log("Retry stopped by predicate");
  } else {
    console.error("Retry failed:", err);
  }
}

Retry with Cancellation Token

import { retryWithCancellationToken } from "smart-retry";

// Create a token object with a 'cancelled' property
const token = { cancelled: false };

// Set up a separate process that can cancel the operation
setTimeout(() => {
  token.cancelled = true;
}, 1000);

try {
  await retryWithCancellationToken(() => fetchSomething(), {
    retries: 5,
    delay: 200,
    cancellationToken: token,
  });
} catch (err) {
  if (token.cancelled) {
    console.log("Retry cancelled via token");
  } else {
    console.error("Retry failed:", err);
  }
}

Retry with Custom Schedule

import { retryWithSchedule } from "smart-retry";

// Will retry after 100ms, then 200ms, then 400ms, then 800ms
await retryWithSchedule(() => fetchSomething(), [100, 200, 400, 800]);

Retry with Predicate Delay

import { retryWithPredicateDelay } from "smart-retry";

await retryWithPredicateDelay(
  () => fetchSomething(),
  (err, attempt) => attempt * 100, // delay increases per attempt
  { retries: 5 },
);

Retry with Randomized Backoff

import { retryWithRandomizedBackoff } from "smart-retry";

await retryWithRandomizedBackoff(() => fetchSomething(), {
  retries: 5,
  delay: 100,
  maxDelay: 2000,
  onRetry: (err, attempt) => {
    console.log(`Randomized backoff retry #${attempt}: ${err}`);
  },
});

Retry with Max Total Attempts

import { retryWithMaxTotalAttempts } from "smart-retry";

let tries = 0;
await retryWithMaxTotalAttempts(
  () => {
    tries++;
    if (tries < 4) throw new Error("Still failing");
    return "success";
  },
  5, // max total attempts (including the first)
  { delay: 100 },
);

Retry with Custom Execution

You can also use a custom execution function to handle the retry logic. This is useful when you want to control how the success or failure of the operation is determined.

import { retryWithCustomExecution, ExecutionResult } from "smart-retry";

// Create a custom execution function that checks return values
// instead of relying on exceptions
async function customAPIExecute<T>(fn: () => Promise<Response>): Promise<ExecutionResult<T>> {
  try {
    const response = await fn();
    // Consider HTTP 2xx status codes as success
    if (response.status >= 200 && response.status < 300) {
      return {
        success: true,
        value: response as unknown as T,
      };
    }
    // Consider any other status code as a failure
    return {
      success: false,
      error: new Error(`API returned status: ${response.status}`),
      value: response as unknown as T, // You can still include the value for the retry handler
    };
  } catch (error) {
    // Handle network errors or other exceptions
    return { success: false, error: error as Error };
  }
}

// Use the custom execution logic with retries
try {
  const result = await retryWithCustomExecution<Response>(() => fetch("https://api.example.com/data"), {
    execute: customAPIExecute,
    retries: 3,
    delay: 200,
    onRetry: (err, attempt) => {
      console.log(`Custom execution retry #${attempt}: ${err.message}`);
    },
  });

  // Process successful result
  const data = await result.json();
} catch (err) {
  console.error("All retry attempts failed:", err);
}

📄 License

MIT © jaktestowac.pl

Powered by jaktestowac.pl team!

🌐 Check out GitHub profile for more open-source projects and resources.