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 🙏

© 2024 – Pkg Stats / Ryan Hefner

do-with-retry

v1.0.3

Published

A module for executing code with retry logic

Downloads

5

Readme

DoWithRetry

This is an easy to use function to help execute code and retry after a certain amount of time if it fails.

In certain scenarios you may want to execute an operation and keep retrying if it fails. For example you may be calling a web service that could fail if you get a bad connection. In that case you'll want to retry, after a small delay, until either the call succeeds or fails enough times that you determine it's pointless to continue.

Features

  • Promise based, works with async code
  • Failure is signaled by simply calling the retry() function that is passed into your user function
  • Comes with a set of built in backoff functions, or create your own
  • Built in TypeScript type definitions
  • No dependencies

Install

npm i do-with-retry

Quick Example

In the simplest example you call doWithRetry passing in a function to execute your code.

const result = await doWithRetry(retry => {
    try {
        return doSomethingThatCouldThrow();
    }
    catch (error) {
        // If at first you don't succeed, try again
        retry(error);
    }
})
.catch(error => {
    console.error("All attempts to do something failed");
    throw error.cause; // throw error that caused the last failure
});

Your function takes one parameter, which is a function to call when you want doWithRetry to try again after waiting a certain amount of time.

When using the default options it will attempt to execute your function a maximum of 10 times and the first retry wait period will be 100ms. The default backoff function is exponential so subsequent attempts will wait for 200ms, 400ms, 800ms, ..., 102400ms.

The doWithRetry Function

The function is asynchronous so you must use await when calling it. It can take two arguments:

  • A user function to execute and retry
  • A set of options to control retry logic (optional)

The function returns the value returned by the user function.

Error handling:

  • If an exception is not caught by the user function doWithRetry will terminate with that exception
  • If the user function fails more than the maximum number of retries it will throw an AttemptCountExceededError
    • To get the last error that caused the user function to fail use error.cause

The User Function

The user function is passed two parameters.

  • The first parameter is a function that is called to signal that the code failed and needs to be retried
  • The second parameter is the attempt number. The initial attempt will be 0, the first retry 1, etc.
result = await doWithRetry((retry, attempt) => {
    try {
        return doSomething();
    }
    catch (err) {
        retry(err);
    }
}

You may also use an async function.

await doWithRetry(async (retry, attempt) => {
    await doSomethingAsync().catch(retry);
})
.catch(err => {
    console.log(err.message); // "Maximum attempt count exceeded"
    throw err.cause;
})

The retry Function

You can call retry with or without an error parameter. If you want to know what error caused the attempt to fail you should pass in the error you catch.

Options

You can pass in options to override the default options. Override as many or few as you want. The following options are available.

  • maxAttempts: Max number of times to attempt calling the function
  • initTimeout: Initial number of ms to wait until retry (note this may be ignored by some backoff functions)
  • getNextTimeout: Function that gets the next time to wait in ms
  • onFail: Function to be called when an attempt fails
  • onSuccess: Function to be called when an attempt succeeds
const options = {
    maxAttempts: 3,
    initTimeout: 100,
    getNextTimeout: linearBackoff(100),
    onFail: (error, attempt) => console.log("Attempt", attempt, "failed", error.message),
    onSuccess: (result, attempt) => console.log("Completed after", attempt, "attempts")
};

const result = await doWithRetry(async (retry) => {
    await doSomething().catch(err => {
        if (err.code === 'ETIMEDOUT') {
            retry(err);
        }
    });
}, options);

Override Default Options

You may want to set your own default options for your entire application. You can override the default options by calling the overrideDefaultOptions function. You can override as many or few of the options as you want. All subsequent calls to doWithRetry will then use these defaults.

overrideDefaultOptions({
    maxAttempts: 5,
    initTimeout: 333,
    getNextTimeout: randomBackoff(1000, 3000)
});

Note: Once you change the default options you can't reset them.

Backoff Functions

A backoff function determines how long doWithRetry will wait before attempting to retry your code. It usually increases upon every retry. This module comes with a number of backoff functions built in.

Constant

Use this backoff when the timeout should be the same on every retry (I know, technically it's not a backoff :P).

constantBackoff(timeout?: number)

  • timeout: The amount to wait between attempts, if not defined the initial timeout from the options will be used
constantBackoff(500); // 500, 500, ...

Linear

The timeout increases linearly. y = mx + b

linearBackoff(increment: number, maxTimeout?: number)

  • increment: The amount to increase timeout on every retry (slope)
  • maxTimeout: An optional max timeout in ms
linearBackoff(500, 3000); // 1000, 1500, 2000, 2500, 3000, 3000, ...

Exponential

The timeout increases exponentially. y = x^n + b

exponentialBackoff(exponent?: number, maxTimeout?: number)

  • exponent: The exponent to use, default is 2
  • maxTimeout: An optional max timeout in ms
exponentialBackoff(2, 10000); // 1000, 2000, 4000, 8000, 10000, 10000, ...

Upward Decay

The timeout increases using upward exponential decay approaching the max timeout. y = max * (1 - (1 / base ^ x))

upwardDecayBackoff(maxTimeout: number, base?: number)

  • maxTimeout: The maximum timeout in ms
  • rate: The rate to increase, must be greater than 1, default is 2

E.g. When the rate is 2, the timeout will increase half way from the current timeout to the max on every retry.

upwardDecayBackoff(1000, 2); // 500, 750, 875, 938, ..., 1000, 1000, ...

Note: When using this the initial timeout defined in options is ignored.

Fibonacci

The timeout increases using the fibonacci sequence.

fibonacciBackoff(factor: number, maxTimeout?: number)

  • factor: An amount to multiply fibonacci number by to get timeout in ms, for seconds use 1000
  • maxTimeout: An optional max timeout
fibonacciBackoff(1000, 8000); // 1000, 2000, 3000, 5000, 8000, 8000, ...

Note: When using this the initial timeout defined in options is ignored.

Random

The timeout is a random number between min and max.

randomBackoff(minTimeout: number, maxTimeout: number)

  • minTimeout: Minimum time to wait in ms
  • maxTimeout: Maximum time to wait in ms
randomBackoff(1000, 5000);

Note: When using this the initial timeout defined in options is ignored.

Build Your Own

You can use your own custom backoff function if you'd like. The function can accept two parameters:

  • curTimeout: The current timeout in ms
  • attempt: The attempt number, on the first retry this will be 1

When attempt is 1 you will usually want to return the current timeout, which is the initial timeout set in options.

In this example the timeout increases by 50% on each retry.

{
    getNextTimeout: (curTimeout, attempt) => {
        if (attempt === 1) return curTimeout;
        else return curTimeout * 1.5;
    }
}

Code Hard