Skip to content

Commit

Permalink
better documentation and customizable decorator
Browse files Browse the repository at this point in the history
  • Loading branch information
martinmoeller committed May 27, 2020
1 parent 71b4cf5 commit 63f4ff1
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 18 deletions.
52 changes: 38 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,33 +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});
```

### Usage as a decorator
This will instantly start calling your function until it returns a resolved promise, no retries are left or a timeout occurred.

You can decorate an exisiting function like this:
If you want to add retries to an existing function, use the decorator:

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

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

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

const result : Promise<string> = decorated("1");
const result: string = await decoratedFunction("1");
```

## Interface
`decorated` is function with the same signature as `asyncFunction`, but will do retries in case of failures.

```typescript
function retry<T>(f: () => Promise<T>, config?: RetryConfig<T>): Promise<T> {}
```

_retry_ will repeatedly call _f_ until a resolved _Promise_ is returned.
Optionally a predicate can be specified, against which the result will be checked.
## Configuration

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

```typescript
export interface RetryConfig<T> {
Expand Down Expand Up @@ -78,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 @@ -91,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: 6 additions & 2 deletions src/retry-promise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,12 @@ 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>) => Promise<T> {
return (...args: Parameters<F>) => retry(() => func(...args), config);
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
Expand Down
28 changes: 26 additions & 2 deletions test/retry-promise.decorator.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {expect} from "./index";
import {customizeRetry, defaultRetryConfig, retry, retryDecorator, wait} from "../src/retry-promise";
import {customizeDecorator, retryDecorator, wait} from "../src/retry-promise";

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

Expand All @@ -8,7 +8,9 @@ describe("Retry decorator test", () => {

const asyncFunctionDecorated = retryDecorator(asyncFunction);

expect(asyncFunctionDecorated("1")).to.eventually.eq("1")
const result = asyncFunctionDecorated("1");

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

it("can use decorator with custom config", async () => {
Expand All @@ -30,4 +32,26 @@ describe("Retry decorator test", () => {
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");
});

});
11 changes: 11 additions & 0 deletions test/retry-promise.demo.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,17 @@ describe("Retry Promise Demo", () => {
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 63f4ff1

Please sign in to comment.