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

reattempt

v0.1.1

Published

Give your functions another chance

Downloads

634

Readme

reattempt

reattempt is a modern JavaScript library for the browser and Node.js that lets you retry asynchronous functions when they fail - because some functions deserve a second chance, or a third or maybe even several dozen or so.

Highlights

  • 🚀 Very lightweight: ~550 bytes minified+gzipped
  • ⚡️ Modern asynchronous JavaScript support with Promises and Async/Await
  • 💪 Flexible API that covers many cases
  • 🛠 Targeted for both the browser and Node.js
  • ⛑ Type-safety with TypeScript and a built-in decorator

Getting Started

To get started, add reattempt to your project:

npm i --save-dev reattempt

Usage

Asynchronous Promise-Based Functions

When an async function (or a function that returns a Promise) is passed to Reattempt.run, the function will be called immediately. If the functions reject with an error, Reattempt.run will retry calling that function. The function will be retried until it resolves, or until the maximum retries count is reached, whichever comes first.

import Reattempt from 'reattempt';

async function doSomethingAsync() {
  // doing async operation that may throw
  return result;
}

async function main() {
  try {
    const result = await Reattempt.run({ times: 3 }, doSomethingAsync);
  } catch (error) {
    // an error is thrown if the function rejects with an error after
    // exhausting all attempts
  }
}

Node.js Error-First Callbacks

Reattempt also works with functions following the error-first callbacks pattern. When working with these functions, instead of passing an async or Promise based function, pass a function with a single argument called done. Use this argument as the error-first callback of your function.

The function will be retried until it returns a value without an error, or until the maximum retries count is reached, whichever comes first.

import fs from 'fs';
import Reattempt from 'reattempt';

async function main() {
  try {
    const data = await Reattempt.run({ times: 3 }, done => {
      fs.readFile('./path/to/file', 'utf8', done);
    });
  } catch (error) {
    // an error is thrown if the function rejects with an error after
    // exhausting all attempts
  }
}

Custom Interface Functions

Similar to working with Node.js Error-First Callbacks, the done callback can be used to reattempt any asynchronous function with custom callback interface. For example, some APIs expects an onSuccess and onError callbacks.

The properties done.resolve and done.reject can be used to hook into any custom interface and perform reattempts as needed.

function doSomething(onSuccess, onError) {
  // some async operations
}

async function main() {
  try {
    const data = await Reattempt.run({ times: 3 }, done => {
      doSomething(done.resolve, done.reject);
    });
  } catch (error) {
    // an error is thrown if the function rejects with an error after
    // exhausting all attempts
  }
}

Intercepting Attempts

There are cases when you need to intercept an attempt call. It's possible to control the reattempt flow, by providing the onError option. This option allows you to intercept each attempt and control the reattempt flow.

import Reattempt from 'reattempt';

async function doSomething() {
  // some async operations
}

function handleError(
  error /* the error object that the function rejected with */,
  done  /* resolves the function call with a custom value */,
  abort /* bail out of remaining attempts and rejects with current error */,
) {
  if (shouldAbortRemainingAttempts) {
    abort();
  } else if (shouldSkipAttemptsAndResolve) {
    done(defaultValue);
  }
}

async function main() {
  try {
    const result = await Reattempt.run(
      { times: 10, onError: handleError },
      doSomething,
    );
  } catch (error) {
    // ...
  }
}

Working with TypeScript

Reattempt As A Decorator

Reattempt also comes as a decorator that can be imported from reattempt/decorator.

import Reattempt from 'reattempt/decorator';

class Group {
  @Reattempt({ times: 3, delay: 5000 })
  private async getUserIds() {
    const user = await fakeAPI.getUsers(this.id); // could throw!
    return users.map(user => user.id);
  }

  public async doSomething() {
    try {
      const result = await this.getUserIds();
    } catch (error) {
      // Only throws after failing 3 attempts with 5 seconds in between
    }
  }
}

Type Safe Callbacks

Reattempt can infer types of async and Promise-based functions automatically.

However, when working with error-first callbacks, you can enforce type safety by passing a type argument informing Reattempt about the list of success arguments the original function could potentially provide.

Reattempt
  .run<[string, string]>({ times: 3 }, done => {
    childProcess.exec('cat *.md | wc -w', attempt);
  })
  // resolves with an array of success type-safe arguments
  .then(([stdout, stderr]) => stdout.trim())
  .catch(error => /* ... */);

API

Methods

run(options: Options, callback: Callback): Promise

Runs and reattempt the provided callback. If the callback fails, it will be reattempted until it resolves, or until the maximum retries count options.times is reached, whichever comes first.

Returns a Promise that resolves with the result of the provided function, and rejects with the same error it could reject with.

Reattempt Options

All Reattempt methods accept an options object as the first argument with the following properties:

times: number

The number of times a function can be reattempted.

If this property is not provided Reattempt will perform the provided function once without any additional reattempts on failure.

delay?: number

The duration in milliseconds between each attempt. Defaults to 0.

If this property is not provided Reattempt will perform a reattempt as soon as the function fails.

onError?(error, done, abort): void

A callback that fires on each attempt after receiving an error. It allows you to intercept an attempt and gives you access to the error object. It passes the following parameters:

  • error: any: the error that the function rejected with
  • done(value: any): void: a function that allows you to skip remaining reattempts and resolve the attempted function with the value provided.
  • abort(): void: a function allowing you to bail out of remaining attempts and rejects the attempted function immediately.

Reattempt Callback

All Reattempt methods take a function as the second argument.

This function will be reattempted on failure and can be one of three forms:

  • An async function.
Reattempt.run({ times: 2 }, async () => {
  // ...
});
  • A function that returns a Promise
Reattempt.run({ times: 2 }, () => {
  return new Promise((resolve, reject) => {
    //...
  });
});
  • A non-async, non-Promise function that wraps functions with error-first-callbacks
Reattempt.run({ times: 2 }, done => {
  fs.readFile('path/to/file', 'utf-8', done);
});

The done Callback

If you are reattempting a non-async function (or a function that does not return a Promise), pass a callback function with one argument done.

This argument controls the reattempt flow and can be used in one of two ways:

  • As an error-first callback that you can pass to any function such as most Node.js APIs
  • As a hook to custom interfaces that expects success and error callbacks by utilizing the two properties done.resolve and done.reject.

License

MIT