Skip to content

Commit

Permalink
add a decorator function (#7)
Browse files Browse the repository at this point in the history
* add a decorator function
  • Loading branch information
normartin authored May 27, 2020
1 parent dbaeb07 commit c2401de
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 8 deletions.
52 changes: 45 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,28 @@ Then you can import it with:
```typescript
import { retry } from 'ts-retry-promise';

const result = await retry(() => Promise.resolve(1), {retries: 3});
const result: number = await retry(() => Promise.resolve(1), {retries: 3});
```

## Interface
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:

```typescript
function retry<T>(f: () => Promise<T>, config?: RetryConfig<T>): Promise<T> {}
import { retryDecorator } from 'ts-retry-promise';

const asyncFunction = async (s: String) => s;

const decoratedFunction = retryDecorator(asyncFunction, {timeout: 1});

const result: string = await decoratedFunction("1");
```

_retry_ will repeatedly call _f_ until a resolved _Promise_ is returned.
Optionally a predicate can be specified, against which the result will be checked.
Here `decoratedFunction` is a function with the same signature as `asyncFunction`, but will do retries in case of failures.

## Configuration

Several aspects of the execution can be configured:
Both `retry` and `retryDecorator` take an optional second argument where you can configure the number of retries and timeouts:

```typescript
export interface RetryConfig<T> {
Expand Down Expand Up @@ -64,6 +73,8 @@ export interface RetryConfig<T> {
_customizeRetry_ returns a new instance of _retry_ that has the defined default configuration.

```typescript
import { customizeRetry } from 'ts-retry-promise';

const impatientRetry = customizeRetry({timeout: 5});

await expect(impatientRetry(async () => wait(10))).to.be.rejectedWith("Timeout");
Expand All @@ -77,9 +88,36 @@ const result = await retryUntilNotEmpty(async () => [1, 2]);
expect(result).to.deep.eq([1, 2]);
```


You can do the same for decorators:
```typescript
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");
```


## Samples ##

_retry_ is well suited for acceptance tests (but not restricted to).
_retryDecorator_ can be used on any function that returns a promise

```typescript
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)

```typescript
// ts-retry-promise/test/retry-promise.demo.test.ts
Expand Down
8 changes: 8 additions & 0 deletions src/retry-promise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ export async function retry<T>(f: () => Promise<T>, config?: Partial<RetryConfig
return timeout(effectiveConfig.timeout, (done) => _retry(f, effectiveConfig, done));
}

export function retryDecorator<T, F extends (...args: any[]) => Promise<T>>(func: F, config?: Partial<RetryConfig<T>>): (...funcArgs: Parameters<F>) => ReturnType<F> {
return (...args: Parameters<F>) => retry(() => func(...args), config) as ReturnType<F>;
}

export function customizeDecorator<T>(customConfig: Partial<RetryConfig<T>>): typeof retryDecorator {
return (args, config) => retryDecorator(args, Object.assign({}, customConfig, config));
}

// tslint:disable-next-line
export function customizeRetry<T>(customConfig: Partial<RetryConfig<T>>): (f: () => Promise<T>, config?: RetryConfig<T>) => Promise<T> {
return (f, c) => {
Expand Down
57 changes: 57 additions & 0 deletions test/retry-promise.decorator.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import {expect} from "./index";
import {customizeDecorator, retryDecorator, wait} from "../src/retry-promise";

describe("Retry decorator test", () => {

it("can use decorator", async () => {
const asyncFunction: (s: string) => Promise<string> = async s => s;

const asyncFunctionDecorated = retryDecorator(asyncFunction);

const result = asyncFunctionDecorated("1");

expect(result).to.eventually.eq("1");
});

it("can use decorator with custom config", async () => {
const asyncFunction: (s: string) => Promise<string> = async s => {
await wait(5);
return s;
};

const asyncFunctionDecorated = retryDecorator(asyncFunction, {timeout: 1});

expect(asyncFunctionDecorated("1")).to.be.rejectedWith("Timeout")
});

it("can use decorator with multiple args", async () => {
const asyncFunction: (s1: string, s2: string) => Promise<string> = async (s1, s2) => s1 + s2;

const asyncFunctionDecorated: (s1: string, s2: string) => Promise<string> = retryDecorator(asyncFunction);

expect(asyncFunctionDecorated("1", "2")).to.eventually.eq("12")
});

it("can customize decorator", async () => {
const asyncFunction = async (s: string) => {
await wait(3);
return s;
};

const myDecorator = customizeDecorator({timeout: 1});

expect(myDecorator(asyncFunction)("1")).to.be.rejectedWith("Timeout");
});

it("can overwrite customized decorator", async () => {
const asyncFunction = async (s: string) => {
await wait(3);
return s;
};

const myDecorator = customizeDecorator({timeout: 1});

expect(myDecorator(asyncFunction, {timeout: 5})("1")).to.eventually.eq("1");
});

});
24 changes: 23 additions & 1 deletion test/retry-promise.demo.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {expect} from "./index";
import {customizeRetry, defaultRetryConfig, retry, wait} from "../src/retry-promise";
import {customizeRetry, defaultRetryConfig, retry, retryDecorator, wait} from "../src/retry-promise";

describe("Retry Promise Demo", () => {

Expand Down Expand Up @@ -75,6 +75,28 @@ describe("Retry Promise Demo", () => {
}
});

it("can use decorator", async () => {

type AsyncFunc = (s: string) => Promise<string>

const asyncFunction: AsyncFunc = async s => s;

const asyncFunctionDecorated: AsyncFunc = retryDecorator(asyncFunction, {timeout: 1});

expect(asyncFunctionDecorated("1")).to.eventually.eq("1")
});

it("decorator demo", async () => {

const loadUserProfile: (id: number) => Promise<{ name: string }> = async id => ({name: "Mr " + id});

const robustProfileLoader = retryDecorator(loadUserProfile, {retries: 2});

const profile = await robustProfileLoader(123);

expect(profile.name).to.eq("Mr 123");
})

});

const browser = {
Expand Down

0 comments on commit c2401de

Please sign in to comment.