Skip to content

lightweight typescript retry decorator with 0 dependency.

License

Notifications You must be signed in to change notification settings

vcfvct/typescript-retry-decorator

Repository files navigation

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.'