Skip to content

About

Maintained drop-in replacement for async-retry: bail that actually stops, async onRetry, shouldRetry, built-in types, ESM + CJS, zero dependencies

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

@tendjs/async-retry

npm version npm downloads bundle CI License

Maintained, drop-in replacement for async-retry.

Calls an async (or sync) function until it succeeds, waiting longer after each failure. Same API and same delays as async-retry, without the retry dependency.

  • Same API as async-retry. Its test suite is ported and runs against both packages
  • Fixes long-standing upstream issues (see below)
  • TypeScript types included
  • ESM and CommonJS
  • Zero dependencies
  • 100% test coverage

Install

npm i @tendjs/async-retry

Migrating from async-retry

- import retry from 'async-retry'
+ import retry from '@tendjs/async-retry'

If you used @types/async-retry, remove it. Types ship with the package, and retry.Options and retry.RetryFunction still work.

To switch without touching your imports, install it under the old name:

npm i async-retry@npm:@tendjs/async-retry@^1

If a dependency of yours uses async-retry, swap it there too:

// package.json
{
  "overrides": { "async-retry": "$async-retry" }, // npm
  "pnpm": { "overrides": { "async-retry": "npm:@tendjs/async-retry@^1" } }, // pnpm
  "resolutions": { "async-retry": "npm:@tendjs/async-retry@^1" } // yarn
}

With npm, $async-retry reuses the version from your own dependencies (npm rejects an override that differs from it). If async-retry is not in your dependencies, use "npm:@tendjs/async-retry@^1" instead.

Usage

import retry from '@tendjs/async-retry'

const data = await retry(
  async (bail) => {
    // if anything throws, we retry
    const res = await fetch('https://example.com')

    if (res.status === 403) {
      // don't retry on 403
      bail(new Error('Unauthorized'))
      return
    }

    return res.text()
  },
  { retries: 5 },
)

require('@tendjs/async-retry') returns the function, like async-retry did. import { retry } from '@tendjs/async-retry' works as well.

API

function retry<T>(
  fn: (bail: (error?: unknown) => void, attempt: number) => T | Promise<T>,
  options?: Options,
): Promise<T>

fn can be async or not. It gets two arguments:

  1. bail(error?): stops retrying and rejects with error, or with Error('Aborted') if you pass nothing. After that, neither fn nor onRetry is called again.
  2. attempt: the attempt number. The first call is 1.

Throwing an error that has bail: true set also stops retrying.

When retries run out, the promise rejects with the error whose message came up most often (ties go to the later one).

Options

Option Default
retries 10 How many times to retry after the first attempt. Infinity retries until success.
factor 2 Exponential factor between delays.
minTimeout 1000 Milliseconds before the first retry.
maxTimeout Infinity Maximum milliseconds between two retries.
randomize true Multiply each delay by a random factor between 1 and 2.
forever false Keep retrying with the longest delay once retries run out.
maxRetryTime Infinity Stop retrying this many milliseconds after the first attempt.
unref false Don't keep the process alive while waiting for a retry.
onRetry(error, attempt) Runs after an attempt fails and before the next one starts. If it returns a promise, the next attempt waits for it. If it throws or rejects, retrying stops with that error.
shouldRetry(error, attempt) Runs after an attempt fails. Return false (or a promise of false) to stop and reject with that error.

Delays are computed like retry does it: minTimeout * factor ** n, times the random factor, capped at maxTimeout.

Fixed upstream issues

Upstream issue Fix Test
#69 bail() stops retrying for good. Upstream kept retrying if the function threw after calling bail(), and a retry that was already scheduled still ran. upstream #69: bail should prevent any further calls to the retrier function
#79 An error thrown in onRetry (or a rejected promise) rejects the retry promise. Upstream lost the error and kept retrying. upstream #79: throw Error inside onRetry
#43 An async onRetry is awaited before the next attempt. upstream #43: Can onRetry handle async functions?
#51 New shouldRetry option. upstream #51: Add option to decide if retry should be attempted?
#36, #97 Types ship with the package and include every option. upstream #36: Types for Typescript, upstream #97: Option has no retries
#103 import { retry } works from ESM and CommonJS. upstream #103: (0 , async_retry_1.retry) is not a function
#92 The docs say onRetry runs before the next attempt, which is what it does. upstream #92: The documentation for onRetry is wrong

Bugs I found while reading the code, without an upstream issue:

  • throw null or throw undefined crashed with a TypeError and, from an async function, left the promise pending forever. Other falsy values (0, '', false) stopped retrying. All of them are now retried like any other error.
  • retries: Infinity hung the process: retry tried to build an infinite list of delays. Now it retries until success, with delays growing up to maxTimeout.
  • Delays above 2^31-1 ms (about 24.8 days) fired after 1 ms, because that is what setTimeout does with them. They are capped instead.
  • unref: true threw a TypeError in browsers, where setTimeout returns a number that has no unref(). The option is now ignored there.

Differences from async-retry

Apart from the fixes above:

  • The options object you pass is not modified. async-retry set options.randomize = true on it.

Any other behaviour difference is a bug. Please open an issue.

Credits

Based on async-retry by Guillermo Rauch and Leo Lamprecht (Vercel). The delay calculation follows retry by Tim Koschützki. See NOTICE.md. Project setup based on antfu/starter-ts.

License

MIT License © Adnan Labiadh

About

Maintained drop-in replacement for async-retry: bail that actually stops, async onRetry, shouldRetry, built-in types, ESM + CJS, zero dependencies

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages