Code Monkey home page Code Monkey logo

ts-retry's Introduction

ts-retry

A little retry tool to execute a function until the function is successful. Can also bind a timeout to a function. This lib is usable in typescript, in javascript, in node, in SPA tools (rest, Vue, Svelte...) and browser (available in ESM and common js format).


Breaking change: To migrate to 3.x: retryAsyncDecorator and retryAsync ahs been move in utils/decorators. These impact only those that import those functions directly from decorator.ts file Other 3.X items are new and implies no breaking change.

For those who are using 1.x in typescript, you may have to add a type to RetryOptions if you want to use the new untilfunction. This type is the called function returns type.


How to

  • to retry something:

    const result = await retry(
      () => {
        /* do something */
      },
      { delay: 100, maxTry: 5 }
    );
  • to retry something async :

    const result = await retryAsync(
      async () => {
        /* do something */
      },
      { delay: 100, maxTry: 5 }
    );
  • to retry until the answer is 42 :

    try {
      await retryAsync(
        async () => {
          /* do something */
        },
        {
          delay: 100,
          maxTry: 5,
          until: (lastResult) => lastResult === 42,
        }
      );
    } catch (err) {
      if (isTooManyTries(err)) {
        // Did not get 42 after 'maxTry' calls
      } else {
        // something else goes wrong
      }
    }
  • Need to call a function at multiple locations with same retryOptions ? Use decorators:

    const fn = (param1: string, param2:number) => /* do something */;
    const decoratedFn = retryDecorator(
      fn,
      { delay:100, maxTry:5 }
    );
    const title1 = await decoratedFn("value1", 1);
    const title2 = await decoratedFn("valueXXX", 2);
    
    const fn = async (name: string): Promise<any> => { /* something async */ };
    const decoratedFn = retryAsyncDecorator(
      fn,
      { delay:100, maxTry:5 }
    );
    const result1 = await decoratedFn("Smith");
    const result2 = await decoratedFn("Doe");
  • to wait:

    await wait(10000); // Wait for 10 seconds
  • to set a timeout:

    try {
      const result = await waitUntil(
        ()=> {/* do something */},
        10000
      );
    } catch (err) {
      if (isTimeoutError(error)) { {
        // fn does not complete after 10 seconds
      } else {
        // fn throws an exception
      }
    }
  • to set a timeout on something async:

    try {
      const result = await waitUntilAsync(async () => {
        /* do something */
      }, 10000);
    } catch (err) {
      if (isTimeoutError(error)) {
        // fn does not complete after 10 seconds
      } else {
        // fn throws an exception
      }
    }
  • Need to call a function at multiple locations with same retryOptions ? Use decorators:

      const fn = (title: string, count:number) => /* a long task */;
      const decoratedFn = waitUntilDecorator(
        fn,
        { delay:100, maxTry:5 }
      );
      const title1 = await decoratedFn("Intro", 1);
      const title2 = await decoratedFn("A chapter", 2);
    const fn = async (name: string): Promise<any> => {
      /* a long task */
    };
    const decoratedFn = waitUntilAsyncDecorator(fn, { delay: 100, maxTry: 5 });
    const result1 = await decoratedFn("John");
    const result2 = await decoratedFn("Doe");

Utils

retry comes with handy utilities function for common use case:

  • to retry until a function returns something defined (aka not null neither not undefined):
  // in all cases results is a string and cannot be null or undefined
  const result = await retryUntilDefined( (): string|undefined => { ... } ) );

  const result = await retryUntilAsyncDefined( (): Promise<string|null> => { ... } );

  const decorated = retryUntilDefinedDecorator( (p1: string): string|undefined => { ... } );
  const result = await decorated('hello world');

  const decorated = retryAsyncUntilDefinedDecorator( (p1: string): Promise<string|undefined> => { ... } );
  const result = await decorated('hello world');
  • to retry until a function returns something truthy:
  // in all cases results is a string and cannot be null or undefined
  const result = await retryUntilTruthy( (): boolean|undefined => { ... } ) );

  const result = await retryAsyncUntilTruthy( (): Promise<number|null> => { ... } );

  const decorated = retryUntilTruthyDecorator( (p1: string): boolean|undefined => { ... } );
  const result = await decorated('hello world');

  const decorated = retryAsyncUntilTruthyDecorator( (p1: string): Promise<boolean|null> => { ... } );
  const result = await decorated('hello world');
  • to retry until fetch is successfully:
  const result = await retryAsyncUntilResponse( () => fetch(...) );

  const decorated = retryAsyncUntilResponseDecorator( (param) => fetch(...) );
  const result = await decorated('q=1');

API

Retry family

  • retry(fn, retryOptions?): call repeatedly fn until fn does not throw an exception. Stop after retryOptions.maxTry count. Between each call wait retryOptions.delay milliseconds. if stop to call fn after retryOptions.maxTry, throws fn exception, otherwise returns fn return value.
  • retryAsync(fn, retryOptions?): same as retry, except fn is an asynchronous function.
  • retryOptions:
    • maxTry: [optional] maximum calls to fn.
    • delay: [optional] delay between each call (in milliseconds). Could be either a number or a function (when delay time dependent from number of retrys, of previous result...), see below for explanation about delay
    • until: [optional] (lastResult) => boolean: return false if last fn results is not the expected one: continue to call fn until until returns true. A TooManyTries is thrown after maxTry calls to fn;
    • onError: [optional](err: Error, currentTry: number) => void: called on each error except the last one. Includes the current try for logging. To catch/log the last error use onMaxRetryFunc
  • onMaxRetryFunc: [optional](err: Error) => void: called on the final error at the maxTry limit only
  • onSuccess: [optional](currentTry: number) => void: called on success. Includes the current try for logging When an option value is not provided, the default one is applied. The default options are:
  delay: 250,
  maxTry: 4 * 60,
  • setDefaultRetryOptions<T>(retryOptions: RetryOptions<T>): change the default retryOptions.
  • getDefaultRetryOptions<T>(): returns the current default retry options.
  • retryAsyncDecorator<T>(fn: T, retryOptions?: RetryOptions<T>) and retryDecorator<T>(fn: T, retryOptions?: RetryOptions<T>): decorators that return a function with same signature than the given function. On decorated call, fn is called repeteadly it does not throw an exception or until retryOptions.maxTry.
  • TooManyTries: an error thrown by retry functions when until returns false after maxTry calls. It comes with a type guard and includes the last failed result:
if (isTooManyTries(error)) {
  // retry failed
  console.error(`last error is ${error.getLastResult()}`)
}

When delay can vary

When delay option is a function, it is called before each retry: this allow to have a delay that can change between retires (ex: delay can increase exponentially). The function receives the following parameters:

(parameter: {
  currentTry: number,
  maxTry: number,
  lastDelay?: number
  lastResult?: RETURN_TYPE
}) => number;

where:

  • currentTry: the number of call to fn (first is 1, not 0).
  • maxTry: maximum calls to fn.
  • lastDelay: the previous delay, undefined when no delay has been computed yet.
  • lastResult: the last result, undefined is last call to fn failed

Until family

retry comes with handy utilities function for common use case:

UntilDefined : To retry until we get a value which is neither null nor undefined.

For calling sync function:

retryUntilDefined<RETURN_TYPE>(
  fn: () => RETURN_TYPE | undefined | null,
  retryOptions?: RetryUtilsOptions,
): Promise<RETURN_TYPE>
retryUntilDefinedDecorator<PARAMETERS_TYPE, RETURN_TYPE>(
  fn: (...args: PARAMETERS_TYPE) => RETURN_TYPE | undefined | null,
  retryOptions?: RetryUtilsOptions,
): (...args: PARAMETERS_TYPE) => Promise<RETURN_TYPE>

For calling async function:

retryAsyncUntilDefined<RETURN_TYPE>(
  fn: () => Promise<RETURN_TYPE | undefined | null>,
  options?: RetryUtilsOptions,
): Promise<RETURN_TYPE>
retryAsyncUntilDefinedDecorator<PARAMETERS_TYPE, RETURN_TYPE>(
  fn: (...args: PARAMETERS_TYPE) => Promise<RETURN_TYPE | undefined | null>,
  retryOptions?: RetryUtilsOptions,
): (...args: PARAMETERS_TYPE) => Promise<RETURN_TYPE>

UntilTruthy : To retry until we get a value which javascript consider as truthy.

For calling sync function:

retryUntilTruthy<PARAMETERS_TYPE, RETURN_TYPE>(
  fn: (...args: PARAMETERS_TYPE) => RETURN_TYPE,
  retryOptions?: RetryUtilsOptions,
): Promise<RETURN_TYPE>
retryUntilTruthyDecorator<PARAMETERS_TYPE,  RETURN_TYPE>(
  fn: (...args: PARAMETERS_TYPE) => RETURN_TYPE,
  retryOptions?: RetryUtilsOptions,
): (...args: PARAMETERS_TYPE) => Promise<RETURN_TYPE>

For calling async function:

retryAsyncUntilTruthy<PARAMETERS_TYPE, RETURN_TYPE>(
  fn: (...args: PARAMETERS_TYPE) => Promise<RETURN_TYPE>,
  retryOptions?: RetryUtilsOptions,
): Promise<RETURN_TYPE>
retryAsyncUntilTruthyDecorator<PARAMETERS_TYPE, RETURN_TYPE>(
  fn: (...args: PARAMETERS_TYPE) => Promise<RETURN_TYPE>,
  retryOptions?: RetryUtilsOptions,
): (...args: PARAMETERS_TYPE) => Promise<RETURN_TYPE>

UntilResponse : To retry until fetch is sucessfull.

retryAsyncUntilResponse<PARAMETERS_TYPE, RETURN_TYPE extends { ok: boolean }>(
  fn: () => Promise<RETURN_TYPE>,
  retryOptions?: RetryUtilsOptions,
): Promise<RETURN_TYPE>
retryAsyncUntilResponseDecorator<PARAMETERS_TYPE, RETURN_TYPE extends { ok: boolean }>(
  fn: (...args: PARAMETERS_TYPE) => Promise<RETURN_TYPE>,
  retryOptions?: RetryUtilsOptions,
): (...args: PARAMETERS_TYPE) => Promise<RETURN_TYPE>

RetryUtilsOptions type is the same than RetryUtilsOptions but without until option.

Delay family

createExponetialDelay Returns a delay function that provide exponetial delais

const delay = createExponetialDelay(20);
const result = await retryAsync(
  async () => {
    /* do something */
  },
  { delay, maxTry: 5 }
);

delay between each try will return 20, 400, 8000, 160000, 3200000

createMutiplicableDelay Returns a delay function that provide multiplicated delais:

createMutiplicableDelay<RETURN_TYPE>(initialDelay: number, multiplicator: number)

First delay retunrs initialDelay, second initialDelaymultiplicator, third multiplicator initialDelay(multiplicator*2) and so on

const delay = createMutiplicableDelay(20);
const result = await retryAsync(
  async () => {
    /* do something */
  },
  { delay, maxTry: 5 }
);

delay will be 20, 60, 120, 180, 240

createRandomDelay Returns a delay function that provide radom delais between given min and max (included):

createRandomDelay<RETURN_TYPE>(min: number, max: number)

Each time the created delay is called, a value between min and max (both included) is generated

const delay = createRandomDelay(500, 10000);
const result = await retryAsync(
  async () => {
    /* do something */
  },
  { delay, maxTry: 5 }
);

delay betewwen each try will be a random value between 500 and 1000 ms.

Wait family

  • wait(duration?): Do nothing during "duration" milliseconds
  • waitUntil(fn, duration?, error?): waitUntil call asynchronously fn once. If fn complete within the duration (express in milliseconds), waitUntil returns the fn result. Otherwise, it throws the given error (if any) or a TimeoutError exception.
  • waitUntilAsync(fn, duration?, error?): same as waitUntil, except fn is an asynchronous function.
  • TimeoutError: an error thrown by waitUntil and waitUntilAsync. It comes with a isTimeoutError type guard:
if (isTimeoutError(error)) {
  // fn does not complete within 10 seconds
}

In case of timeout fn is still executing. It is advised to add a mean to abort it.

  • When duration is not provided, the default one is applied. The default is 60000ms.
  • setDefaultDuration(duration: number): change the default duration.
  • getDefaultDuration(): returns the current default duration.
  • waitUntilAsyncDecorator(fn: T, duration?: number, error?: Error) and waitUntilDecorator(fn: T, duration?: number, error?: Error): decorators that return a function with same signature than the given function. On decorated call, fn is called bounded to the duration.

Custom reaction when max retry is achieved

Sometimes, you need to perform some actions when max retry has achieved and the error is still there. For this onMaxRetryFunc?: (err: Error) => void; optional function was added to RetryOptions. For example, you would like to store results of the error into the file in order to process it later. Here's how you can do it :

export const runWithRetry = <T>(
  message: string,
  serviceUnderTest: ServiceUnderTest,
  fn: () => T | Promise<T>,
  delay = 1000,
  maxTry = 10
) => {
  const saveErrorReport = (err) => {
    const errorDetails = {
      serviceName: serviceUnderTest.connectorName,
      error: err.message as string,
      description: `Failed to ${message} because of ${err.message as string}`,
      errorName: err.name as string,
      stack: err.stack as string,
    };
    const path = resolve(
      __dirname,
      `../../../failed-service-report/${serviceUnderTest.connectorName}.json`
    );
    writeFile(path, Buffer.from(JSON.stringify(errorDetails)));
  };
  return retryAsync(
    async () => {
      logger.info(`${serviceUnderTest.description}: ${message}`);
      return fn();
    },
    {
      delay,
      maxTry,
      onMaxRetryFunc: saveErrorReport,
    }
  );
};

Compatibility

This lib works with Deno (to import it,use the url https://raw.githubusercontent.com/franckLdx/ts-retry/<version>/src/index.ts). However, it's more convenient to use the specific port of this lib to Deno: see https://deno.land/x/retry

ts-retry's People

Contributors

azakordonets avatar franckldx avatar jamescqcampbell avatar joaquingimenez1 avatar realech avatar tmoran-stenoa avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

ts-retry's Issues

cannot import createExponetialDelay

this doesn't work:

import {retryAsync, createExponentialDelay} from 'ts-retry';

is my import wrong? im getting typescript 'no exported member' error

add 'on error'

can we have 'onError' parameter (optional) that will be called every time the 'action' fails? could be good to use with the logging system.

Access attempt number?

Thank you for this brilliant library. Can you consider providing the attempt / try number when logging the error message?

exponential backoff and and policy basing on error type

41b3577486839d799e669632927cdd12

I would like to propose two features:

  1. exponential backoff - possible to realize by function with delay time dependent from number of retrys
  2. instant reject or resolve for some error types - possible by adding layer with error processing before next iteration

retryUntilDefined retries also on error

retryUntilDefined and retryAsyncUntilDefined seem to retry also on error. Is there any way to make them retry only when undefined or null is returned but immediately bail out on any error?

retryAsyncDecorator is not exported to API interface

It can be used in JS code only providing a full path like:

import {retryAsyncDecorator} from 'ts-retry/lib/esm/retry/utils/decorators';

But Jest test running the code importing it fails with

    Jest encountered an unexpected token

    Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.

    Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.

    By default "node_modules" folder is ignored by transformers.

    Here's what you can do:
     • If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/ecmascript-modules for how to enable it.
     • If you are trying to use TypeScript, see https://jestjs.io/docs/getting-started#using-typescript
     • To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
     • If you need a custom transformation specify a "transform" option in your config.
     • If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.

    You'll find more details and examples of these config options in the docs:
    https://jestjs.io/docs/configuration
    For information about custom transformations, see:
    https://jestjs.io/docs/code-transformation

    Details:

    node_modules/ts-retry/lib/esm/retry/utils/decorators/index.js:1
    ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){export { retryDecorator, retryAsyncDecorator } from './decorators';
                                                                                      ^^^^^^

typings.d.ts not found

Hi. The latest npm package (1.4.0) can't be used on React.

The latest version working is 1.1.3

Great job btw!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.