Giter Club home page Giter Club logo

typescript-retry-decorator's Introduction

Retry

A simple retry decorator for typescript with 0 dependency.

This is inspired by the Spring-Retry project. Written in Typescript, 100% Test Coverage.

Import and use it. Retry for Promise is supported as long as the runtime has promise(nodejs/evergreen-browser).

Install

npm install typescript-retry-decorator

Depending on your setup, you may need to enable experimentalDecorators flag in tsconfig.json.

Options

Option Name Type Required? Default Description
maxAttempts number Yes - The max attempts to try
backOff number No 0 number in ms to back off. If not set, then no wait
backOffPolicy enum No FixedBackOffPolicy can be fixed or exponential
exponentialOption object No { maxInterval: 2000, multiplier: 2 } This is for the ExponentialBackOffPolicy
The max interval each wait and the multiplier for the backOff. For backoffStrategy, more details below
doRetry (e: any) => boolean No - Function with error parameter to decide if repetition is necessary.
value Error/Exception class No [ ] An array of Exception types that are retryable.
useConsoleLogger boolean No true Print errors on console.
useOriginalError throw original exception No false MaxAttemptsError by default. if this is set to true, the original exception would be thrown instead.

Exponential options

The exponentialOption allows you to fine-tune the exponential backoff strategy using several options:

  • maxInterval: The maximum interval between two retries. The default value is 2000 ms.
  • multiplier: The multiplier to use to generate the next backoff interval from the last one. The default value is 2.
  • backoffStrategy: Optional. If specified, determines the strategy used to introduce "jitter" between retry intervals. For an explanation of the available strategies and why you might select one over the other, check out this article.
    • ExponentialBackoffStrategy.FullJitter: The base backoff interval is multiplied by a random number between 0 and 1.
    • ExponentialBackoffStrategy.EqualJitter: The backoff interval is (base interval / 2) + (random value between 0 and base interval / 2).

Example

import { Retryable, BackOffPolicy } from 'typescript-retry-decorator';

let count: number = 1;

class RetryExample {
  @Retryable({ maxAttempts: 3 })
  static async noDelayRetry() {
    console.info(`Calling noDelayRetry for the ${count++} time at ${new Date().toLocaleTimeString()}`);
    throw new Error('I failed!');
  }

  @Retryable({ 
    maxAttempts: 3, 
    value: [SyntaxError, ReferenceError]
  })
  static async noDelaySpecificRetry(): Promise<void> {
    console.info(`Calling noDelaySpecificRetry for the ${count++} time at ${new Date().toLocaleTimeString()}`);
    throw new SyntaxError('I failed with SyntaxError!');
  }

  @Retryable({ 
    maxAttempts: 3,
    backOff: 1000,
    doRetry: (e: Error) => {
      return e.message === 'Error: 429';
    }
   })
  static async doRetry() {
    console.info(`Calling doRetry for the ${count++} time at ${new Date().toLocaleTimeString()}`);
    throw new Error('Error: 429');
  }

  @Retryable({ 
    maxAttempts: 3,
    backOff: 1000,
    doRetry: (e: Error) => {
      return e.message === 'Error: 429';
    }
   })
  static async doNotRetry() {
    console.info(`Calling doNotRetry for the ${count++} time at ${new Date().toLocaleTimeString()}`);
    throw new Error('Error: 404');
  }

  @Retryable({
    maxAttempts: 3,
    backOffPolicy: BackOffPolicy.FixedBackOffPolicy,
    backOff: 1000
  })
  static async fixedBackOffRetry() {
    console.info(`Calling fixedBackOffRetry 1s for the ${count++} time at ${new Date().toLocaleTimeString()}`);
    throw new Error('I failed!');
  }

  @Retryable({
    maxAttempts: 3,
    backOffPolicy: BackOffPolicy.ExponentialBackOffPolicy,
    backOff: 1000,
    exponentialOption: { maxInterval: 4000, multiplier: 3 }
  })
  static async ExponentialBackOffRetry() {
    console.info(`Calling ExponentialBackOffRetry backOff 1s, multiplier=3 for the ${count++} time at ${new Date().toLocaleTimeString()}`);
    throw new Error('I failed!');
  }

  @Retryable({
    maxAttempts: 3,
    backOffPolicy: BackOffPolicy.ExponentialBackOffPolicy,
    backOff: 1000,
    exponentialOption: { maxInterval: 4000, multiplier: 2, backoffStrategy: ExponentialBackoffStrategy.EqualJitter }
  })
  static async ExponentialBackOffWithJitterRetry() {
    console.info(`Calling ExponentialBackOffWithJitterRetry backOff 1s, multiplier=2 for the ${count++} time at ${new Date().toLocaleTimeString()}`);
    throw new Error('I failed!');
  }
}

(async () => {
  try {
    resetCount();
    await RetryExample.noDelayRetry();
  } catch (e) {
    console.info(`All retry done as expected, final message: '${e.message}'`);
  }

  try {
    resetCount();
    await RetryExample.doRetry();
  } catch (e) {
    console.info(`All retry done as expected, final message: '${e.message}'`);
  }

  try {
    resetCount();
    await RetryExample.doNotRetry();
  } catch (e) {
    console.info(`All retry done as expected, final message: '${e.message}'`);
  }

  try {
    resetCount();
    await RetryExample.fixedBackOffRetry();
  } catch (e) {
    console.info(`All retry done as expected, final message: '${e.message}'`);
  }

  try {
    resetCount();
    await RetryExample.ExponentialBackOffRetry();
  } catch (e) {
    console.info(`All retry done as expected, final message: '${e.message}'`);
  }

  try {
    resetCount();
    await RetryExample.ExponentialBackOffWithJitterRetry();
  } catch (e) {
    console.info(`All retry done as expected, final message: '${e.message}'`);
  }

})();

function resetCount() {
  count = 1;
}

Run the above code with ts-node, then output will be:

Calling noDelayRetry for the 1 time at 4:12:49 PM
Calling noDelayRetry for the 2 time at 4:12:49 PM
Calling noDelayRetry for the 3 time at 4:12:49 PM
Calling noDelayRetry for the 4 time at 4:12:49 PM
I failed!
All retry done as expected, final message: 'Failed for 'noDelayRetry' for 3 times.'
Calling noDelayRetry for the 1 time at 4:12:49 PM
Calling noDelayRetry for the 2 time at 4:12:49 PM
Calling noDelayRetry for the 3 time at 4:12:49 PM
Calling noDelayRetry for the 4 time at 4:12:49 PM
I failed with SyntaxError!
All retry done as expected, final message: 'Failed for 'noDelaySpecificRetry' for 3 times.'
Calling doRetry for the 1 time at 4:12:49 PM
Calling doRetry for the 2 time at 4:12:50 PM
Calling doRetry for the 3 time at 4:12:51 PM
Calling doRetry for the 4 time at 4:12:52 PM
Error: 429
All retry done as expected, final message: 'Failed for 'doRetry' for 3 times.'
Calling doNotRetry for the 1 time at 4:12:52 PM
All retry done as expected, final message: 'Error: 404'
Calling fixedBackOffRetry 1s for the 1 time at 4:12:52 PM
Calling fixedBackOffRetry 1s for the 2 time at 4:12:53 PM
Calling fixedBackOffRetry 1s for the 3 time at 4:12:54 PM
Calling fixedBackOffRetry 1s for the 4 time at 4:12:55 PM
I failed!
All retry done as expected, final message: 'Failed for 'fixedBackOffRetry' for 3 times.'
Calling ExponentialBackOffRetry backOff 1s, multiplier=3 for the 1 time at 4:12:55 PM
Calling ExponentialBackOffRetry backOff 1s, multiplier=3 for the 2 time at 4:12:56 PM
Calling ExponentialBackOffRetry backOff 1s, multiplier=3 for the 3 time at 4:12:59 PM
Calling ExponentialBackOffRetry backOff 1s, multiplier=3 for the 4 time at 4:13:03 PM
I failed!
All retry done as expected, final message: 'Failed for 'ExponentialBackOffRetry' for 3 times.'
Calling ExponentialBackOffWithJitterRetry backOff 1s, multiplier=2 for the 1 time at 4:13:03 PM
Calling ExponentialBackOffWithJitterRetry backOff 1s, multiplier=2 for the 2 time at 4:13:03 PM
Calling ExponentialBackOffWithJitterRetry backOff 1s, multiplier=2 for the 3 time at 4:13:05 PM
Calling ExponentialBackOffWithJitterRetry backOff 1s, multiplier=2 for the 4 time at 4:13:09 PM
I failed!
All retry done as expected, final message: 'Failed for 'ExponentialBackOffWithJitterRetry' for 3 times.'

typescript-retry-decorator's People

Contributors

aleborrego avatar dependabot[bot] avatar ivan-kleshnin avatar kyeong5 avatar mananruck avatar mastrzyz avatar smcroskey avatar vcfvct 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  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  avatar

typescript-retry-decorator's Issues

Allow custom handling of last exception

It would be helpful to have the ability to handle what gets thrown in the end, in case there is a need to remap the error to something else. I did not see this functionality in the documentation, but perhaps it is already possible?

And thanks for making this library!

support for retrying custom defined errors extended from Error class

Use Case

I need to pass custom defined error classes on the value field of the RetryOptions options object:

Code that reproduces error:

import { Retryable, BackOffPolicy} from "typescript-retry-decorator"

class CustomError extends Error {
  constructor(message?: string | undefined) {
    super(message)
    Object.setPrototypeOf(this, CustomError.prototype)
    this.name = 'CustomError'
  }
}

class Test {
   @Retryable({
    maxAttempts: 3,
    backOffPolicy: BackOffPolicy.FixedBackOffPolicy,
    backOff: 1000,
    value: [CustomError],
  })
    public async test() {
        throw new CustomError()
    }
}

Error

Type 'typeof CustomError' is not assignable to type 'ErrorConstructor'.
  Type 'typeof CustomError' provides no match for the signature '(message?: string | undefined): Error'

My build target is esnext

Could someone provide an example where a custom error class is passed to the value field of the RetryOptions object? The tests and examples do not include such case

Add option to throw the original exception

I know the original error message is included when retries are exhausted, but it would be great to have the option to bubble up the original error instance itself stack trace and all. This would, of course, not be expected to be enabled by default as it would be a breaking change to existing functionality, but I imagine it could be enabled via the options.

Add option to include original exception message?

Would be fantastic to include the original exception message that caused the final retry

Currently we see

Error: Failed for 'foo' for 2 times.

Would like to see

Error: Failed for 'fo' for 2 times . Orginal Error : 'Null PTR Exception'

Add an option to retry specific error classes

A common use case is to retry errors of certain types like FetchError or similar. doRetry does the job, but it is inconvenient for such a common use case.

Is it possible to add an option like errors: ErrorConstructor[] | ErrorConstructor to specify which error classes need to be retried?

How does this plugin handle the last exception?

Hello, how does this plugin handle the last exception? For example, I set to retry 3 times. After 3 times, a 503 exception still occurs, and an email is sent. But after the plugin retries 3 times, I can't catch the exception, and I can't send the email. How to deal with this scenario?

TypeScript issue

Hi there! Seems like a nice and useful library but, unfortunately, I'm getting the following type error:
Screenshot 2023-07-23 at 12 04 50

I played with different TSconfig & package.json setups, tweaking this and that, but still can't even say if it's a deterministic issue or something specific to my IDE. Sometimes it appears and sometimes โ€“ not.
The above screenshot uses $ tsc --init > tsconfig.json version ๐Ÿค” Nothing fancy.

I realize it's hard to help me without a reproduceable code.
My hope is that this error might look familiar to the authors. Otherwise I'll try to find time to come up with a better demo.

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.