Skip to content

Promise-based function retry utility.

License

Notifications You must be signed in to change notification settings

SEAPUNK/recaller

Repository files navigation

recaller

npm version CI codecov

Promise-based function retry utility. Designed for async/await.

npm install recaller


usage

example partially stolen from async-retry's example

import recaller, { constantBackoff } from "recaller";
import fetch from "node-fetch";

export default async function fetchSomething() {
  return await recaller(
    async (bail, attempt) => {
      const res = await fetch("https://google.com");

      if (403 === res.status) {
        // we're not going to retry
        return bail(new Error("Unauthorized"));
      }

      const data = await res.text();
      return data.substr(0, 500);
    },
    {
      // default: 2 retries
      retries: 10,
      // default: no backoff, retry immediately
      backoff: constantBackoff(1000),
    },
  );
}

api

The code is fully TSDoc'd. See src/index.ts for documentation of the main functions, listed below:

  • recaller(fn, opts)
  • constantBackoff(ms)
  • fullJitterBackoff(opts)

backoffs

recaller doesn't backoff (wait before retrying) by default. To specify backoff, you must give it a backoff function in the options (opts.backoff).

example:

import recaller, { constantBackoff } from 'recaller'

export default function doSomething () {
  return await recaller(async () => {
    const res = await fetch('https://google.com')
  }, {
    // on every failure, wait 5 seconds before retrying
    backoff: constantBackoff(5000)
  })
}

A backoff function, given an attempt count, returns the next delay to wait in milliseconds. For example, constantBackoff(ms) below:

function constantBackoff(ms) {
  ms = ms ?? 5000;
  return (attempt) => ms;
}

recaller comes with 5 backoff generator functions, inspired by AWS's exponential backoff blog post.

Use fullJitterBackoff for most cases, as it generally gives you the best results. You only really have to tweak the base and cap with it. See code for more documentation.

  • constantBackoff(ms)
  • fullJitterBackoff({base, cap, factor})

the following aren't recommended, and only exist for completeness:

  • exponentialBackoff({base, cap, factor})
  • equalJitterBackoff({base, cap, factor})
  • decorrelatedJitterBackoff({base, cap, times})

handling retries

You can intercept each retry attempt, by providing a function in opts.onretry.

import recaller from 'recaller'

export default function doSomething () {
  return await recaller(async () => {
    const res = await fetch('https://google.com')
  }, {
    onretry: function (err, attempt, delayTime) {
      // Prevent retries; reject the recaller with the last error
      if (err instanceof TypeError) throw err

      // err is the error of the attempt
      // attempt is the attempt #. If the first call failed, then attempt = 1.
      // delayTime is how long we will wait before next attempt.

      logger.warn(`doSomething attempt ${attempt} failed;
        will wait ${delayTime} ms before trying again.
        error: ${err}
      `)
    }
  })
}