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).
npm install typescript-retry-decorator
Depending on your setup, you may need to enable experimentalDecorators
flag in tsconfig.json
.
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. |
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).
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.'