Skip to content

Commit

Permalink
add ability to specify backoff delay calculation function
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-okrushko committed May 16, 2018
1 parent 93c1b68 commit bddb11d
Show file tree
Hide file tree
Showing 10 changed files with 64 additions and 53 deletions.
Binary file added .DS_Store
Binary file not shown.
19 changes: 9 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
# backoff-rxjs
A collection of helpful RxJS operators to deal with backoff strategies (like exponential backoff)

## intervalExponential
![Basic interval Exponential](./intervalExponentialBasic.svg)

`intervalExponential` works similiarly to `interval` except that it doubles the delay between emissions every time.
## intervalBackoff
![Basic interval backoff](./intervalBackoffBasic.svg)

`intervalBackoff` works similiarly to `interval` except that it doubles the delay between emissions every time.

| name | type | attirbute | description |
| ------------- |-------------| -----| ---------------|
| config | [number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number) \| [IntervalExponentialConfig](https://github.com/alex-okrushko/backoff-rxjs/blob/277fbbbde4b046733070e2ed64e0b765699fb66b/src/observable/intervalExponential.ts#L6)| required |Can take number as initial interval or a config with initial interval and optional max Interval |
| config | [number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number) \| [IntervalBackoffConfig]()| required |Can take number as initial interval or a config with initial interval, optional max Interval and optional backoff delay function (exponential by default) |

`intervalExponential` is especially useful for periodic polls that are reset whenever user activity is detected:
`interval` is especially useful for periodic polls that are reset whenever user activity is detected:
```ts
fromEvent(document, 'mousemove').pipe(

Expand All @@ -23,14 +22,14 @@ fromEvent(document, 'mousemove').pipe(
startWith(null),

// Resetting exponential interval
switchMapTo(intervalExponential({initialInterval: LOAD_INTERVAL_MS, maxInterval: MAX_INTERVAL_MS})),
switchMapTo(intervalBackoff({initialInterval: LOAD_INTERVAL_MS, maxInterval: MAX_INTERVAL_MS})),
);
```


## retryExponentialBackoff
![Retry Backoff Exponential Image](./retryBackoffExponential.svg)
## retryBackoff
![Retry Backoff Exponential Image](./retryBackoff.svg)

| name | type | attirbute | description |
| ------------- |-------------| -----| ---------------|
| config | [number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number) \| [IntervalExponentialConfig](https://github.com/alex-okrushko/backoff-rxjs/blob/277fbbbde4b046733070e2ed64e0b765699fb66b/src/observable/intervalExponential.ts#L6)| required |Can take number as initial interval or a config with initial interval and optional max Interval |
| config | [number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number) \| [RetryBackoffConfig]()| required |Can take number as initial interval or a config with initial interval, optional max Interval, optional max number of retry attempts, optional function to cancel reties and optional backoff delay function (exponential by default) |
2 changes: 1 addition & 1 deletion intervalExponentialBasic.svg → intervalBackoffBasic.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "backoff-rxjs",
"version": "0.0.3",
"version": "0.0.4",
"description": "A collection of helpful RxJS operators to deal with backoff strategies (like exponential backoff)",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion retryBackoffExponential.svg → retryBackoff.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export {retryBackoffExponential, RetryBackoffExponentialConfig} from './operators/retryBackoffExponential';
export {intervalBackoffExponential, IntervalBackoffExponentialConfig} from './observable/intervalBackoffExponential';
export {retryBackoff, RetryBackoffConfig} from './operators/retryBackoff';
export {intervalBackoff, IntervalBackoffConfig} from './observable/intervalBackoff';
33 changes: 33 additions & 0 deletions src/observable/intervalBackoff.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Observable, of, timer } from "rxjs";
import { expand, mapTo } from "rxjs/operators";

import { defaultBackoffDelay, getDelay } from "../utils";

export interface IntervalBackoffConfig {
initialInterval: number;
maxInterval?: number;
backoffDelay: (iteration: number, initialInterval: number) => number;
}
/**
* Creates an Observable that emits sequential numbers with by default
* exponentially increasing interval of time.
*/
export function intervalBackoff(
config: number | IntervalBackoffConfig
): Observable<number> {
const {
initialInterval,
maxInterval = Infinity,
backoffDelay = defaultBackoffDelay
} =
typeof config === "number" ? { initialInterval: config } : config;
return of(0).pipe(
// Expend starts with number 1 and then recursively
// projects each value to new Observable and puts it back in.
expand(iteration =>
timer(getDelay(backoffDelay(iteration, initialInterval), maxInterval))
// Once timer is complete, iteration is increased
.pipe(mapTo(iteration + 1))
)
);
}
26 changes: 0 additions & 26 deletions src/observable/intervalBackoffExponential.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import {iif, interval, Observable, throwError, timer, zip} from 'rxjs';
import {concatMap, retryWhen} from 'rxjs/operators';

import {getDelay} from '../utils';
import {getDelay, defaultBackoffDelay} from '../utils';

export interface RetryBackoffExponentialConfig<E> {
export interface RetryBackoffConfig<E> {
initialInterval: number;
maxAttempts?: number;
maxInterval?: number;
cancelRetry?: (error: E) => boolean;
backoffDelay: (iteration: number, initialInterval: number) => number;
}

/**
Expand All @@ -18,23 +19,22 @@ export interface RetryBackoffExponentialConfig<E> {
* resubscriptions (if provided). Retry can be cancelled at any point if
* cancelRetry condition is met.
*/
export function retryBackoffExponential<E>(
config: number|RetryBackoffExponentialConfig<E>):
export function retryBackoff<E>(
config: number|RetryBackoffConfig<E>):
<T>(source: Observable<T>) => Observable<T> {
const {
initialInterval,
maxAttempts = Infinity,
maxInterval = Infinity,
cancelRetry = () => false,
backoffDelay = defaultBackoffDelay,
} = (typeof config === 'number') ? {initialInterval: config} : config;
return <T>(source: Observable<T>) => source.pipe(
retryWhen<T>(errors => zip(errors, interval(0)).pipe(
// [error, i] come from 'errors' observable
concatMap(([error, i]) => iif(
() =>
i < maxAttempts && !cancelRetry(error),
timer(getDelay(
i, initialInterval, maxInterval)),
() => i < maxAttempts && !cancelRetry(error),
timer(getDelay(backoffDelay(i, initialInterval), maxInterval)),
throwError(error),
),
),
Expand Down
13 changes: 9 additions & 4 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
export function getDelay(
iteration: number, initialInterval: number, maxInterval: number) {
const currentDelay = Math.pow(2, iteration) * initialInterval;
return Math.min(currentDelay, maxInterval);
/** Calculates the actual delay which can be limited by maxInterval */
export function getDelay(backoffDelay: number, maxInterval: number) {
return Math.min(backoffDelay, maxInterval);
}

/** Default backoff strategy is exponential delay */
export function defaultBackoffDelay(
iteration: number, initialInterval: number) {
return Math.pow(2, iteration) * initialInterval;
}

0 comments on commit bddb11d

Please sign in to comment.