What is ts-retry-promise?
The ts-retry-promise npm package provides a way to retry promises with customizable options. It is useful for handling transient errors in asynchronous operations by retrying the operation until it succeeds or a maximum number of retries is reached.
What are ts-retry-promise's main functionalities?
Basic Retry
This feature allows you to retry a promise-based function a specified number of times. In this example, the fetchData function simulates an API call that may fail. The retry function will attempt to call fetchData up to 3 times before giving up.
const retry = require('ts-retry-promise');
const fetchData = () => {
return new Promise((resolve, reject) => {
// Simulate an API call that may fail
if (Math.random() > 0.5) {
resolve('Data fetched successfully');
} else {
reject('Fetch failed');
}
});
};
retry(fetchData, { retries: 3 })
.then(result => console.log(result))
.catch(err => console.error(err));
Exponential Backoff
This feature allows you to retry a promise-based function with exponential backoff. The delay between retries increases exponentially. In this example, the initial delay is 1000ms and it doubles with each retry.
const retry = require('ts-retry-promise');
const fetchData = () => {
return new Promise((resolve, reject) => {
// Simulate an API call that may fail
if (Math.random() > 0.5) {
resolve('Data fetched successfully');
} else {
reject('Fetch failed');
}
});
};
retry(fetchData, { retries: 3, delay: 1000, factor: 2 })
.then(result => console.log(result))
.catch(err => console.error(err));
Custom Retry Logic
This feature allows you to define custom logic to determine if a promise should be retried. In this example, the shouldRetry function checks if the error message is 'Fetch failed' to decide whether to retry the fetchData function.
const retry = require('ts-retry-promise');
const fetchData = () => {
return new Promise((resolve, reject) => {
// Simulate an API call that may fail
if (Math.random() > 0.5) {
resolve('Data fetched successfully');
} else {
reject('Fetch failed');
}
});
};
const shouldRetry = (err) => {
// Custom logic to determine if the error is retryable
return err === 'Fetch failed';
};
retry(fetchData, { retries: 3, shouldRetry })
.then(result => console.log(result))
.catch(err => console.error(err));
Other packages similar to ts-retry-promise
promise-retry
The promise-retry package provides similar functionality for retrying promises. It allows for customizable retry strategies, including exponential backoff. Compared to ts-retry-promise, promise-retry offers a more flexible API for defining retry strategies.
async-retry
The async-retry package is another alternative for retrying asynchronous operations. It supports various retry strategies and is highly configurable. It is similar to ts-retry-promise but offers additional features like custom retry strategies and more detailed error handling.
retry
The retry package provides a comprehensive set of tools for retrying operations. It supports both synchronous and asynchronous retries, with customizable options for delay, retries, and backoff strategies. It is more feature-rich compared to ts-retry-promise, making it suitable for more complex retry scenarios.
ts-retry-promise
![NPM](https://img.shields.io/npm/v/ts-retry-promise.svg?color=#555)
retry for functions returning a promise
Changelog
Usage
Install with yarn:
yarn add ts-retry-promise
Install with npm:
npm install --save ts-retry-promise
Then you can import it with:
import { retry } from 'ts-retry-promise';
const result: number = await retry(() => Promise.resolve(1), {retries: 3});
This will instantly start calling your function until it returns a resolved promise, no retries are left or a timeout occurred.
If you want to add retries to an existing function, use the decorator:
import { retryDecorator } from 'ts-retry-promise';
const asyncFunction = async (s: String) => s;
const decoratedFunction = retryDecorator(asyncFunction, {timeout: 1});
const result: string = await decoratedFunction("1");
Here decoratedFunction
is a function with the same signature as asyncFunction
, but will do retries in case of failures.
Configuration
Both retry
and retryDecorator
take an optional second argument where you can configure the number of retries and timeouts:
export interface RetryConfig<T> {
retries?: number | "INFINITELY";
delay?: number;
until?: (t: T) => boolean;
logger?: (msg: string) => void;
timeout?: number | "INFINITELY";
backoff?: "FIXED" | "EXPONENTIAL" | "LINEAR" | ((attempt: number, delay: number) => number);
maxBackOff?: number;
retryIf: (error: any) => boolean
}
Customize
customizeRetry returns a new instance of retry that has the defined default configuration.
import { customizeRetry } from 'ts-retry-promise';
const impatientRetry = customizeRetry({timeout: 5});
await expect(impatientRetry(async () => wait(10))).to.be.rejectedWith("Timeout");
const retryUntilNotEmpty = customizeRetry({until: (array: any[]) => array.length > 0});
const result = await retryUntilNotEmpty(async () => [1, 2]);
expect(result).to.deep.eq([1, 2]);
You can do the same for decorators:
import { customizeDecorator } from 'ts-retry-promise';
const asyncFunction = async (s: string) => {
await wait(3);
return s;
};
const impatientDecorator = customizeDecorator({timeout: 1});
expect(impatientDecorator(asyncFunction)("1")).to.be.rejectedWith("Timeout");
Failure
In case retry
failed, an error is thrown.
You can access the error that occurred the last time the function has been retried via the property lastError
:
retry(async () => throw "1")
.catch(err => console.log(err.lastError));
NotRetryableError
Wrapped function can throw NotRetryableError
if retrying need to be stopped eventually:
import { NotRetryableError } from 'ts-retry-promise';
retry(async () => throw new NotRetryableError("This error"))
.catch(err => console.log(err.lastError), { retries: 'INFINITELY' });
Samples
retryDecorator can be used on any function that returns a promise
const loadUserProfile: (id: number) => Promise<{ name: string }> = async id => ({name: "Mr " + id});
const robustProfileLoader = retryDecorator(loadUserProfile, {retries: 2});
const profile = await robustProfileLoader(123);
retry is well suited for acceptance tests (but not restricted to)
it("will retry until no exception or limit reached", async () => {
await retry(async () => {
const title = await browser.$("h1");
expect(title).to.eq("Loaded");
});
});
it("can return a result", async () => {
const pageTitle = await retry(async () => {
const title = await browser.$("h1");
expect(title).to.be.not.empty;
return title;
});
expect(pageTitle).to.eq("Loaded");
});
it("can be configured and has defaults", async () => {
await retry(async () => {
}, {backoff: "LINEAR", retries: 100});
});
it("will retry until condition is met or limit reached", async () => {
await retry(
() => browser.$$("ul"),
{until: (list) => list.length === 2});
});
it("can have a timeout", async () => {
const promise = retry(
() => wait(100),
{timeout: 10},
);
await expect(promise).to.be.rejectedWith("Timeout");
});
it("can create a customized retry", async () => {
const impatientRetry = customizeRetry({timeout: 5});
await expect(impatientRetry(async () => wait(10))).to.be.rejectedWith("Timeout");
});
it("can create another customized retry", async () => {
const retryUntilNotEmpty = customizeRetry({until: (array: number[]) => array.length > 0});
const result = await retryUntilNotEmpty(async () => [1, 2]);
expect(result).to.deep.eq([1, 2]);
});
it("can customize default config", async () => {
const originalTimeout = defaultRetryConfig.timeout;
try {
defaultRetryConfig.timeout = 1;
await expect(retry(async () => wait(10))).to.be.rejectedWith("Timeout");
} finally {
defaultRetryConfig.timeout = originalTimeout;
}
});
Release instructions
Release automation has been setup according this guide.
- Create a Github release with version tag like
0.6.1
. - Check the new version exists on npmjs.com/package/ts-retry-promise and has
latest
tag.