poli-c

A fault tolerance utility for JavaScript.

Usage no npm install needed!

<script type="module">
  import poliC from 'https://cdn.skypack.dev/poli-c';
</script>

README

Poli-C

GitHub license npm version

Poli-C (pronounced "policy") is a fault tolerance utility for JavaScript. Inspired by Polly for .NET, this library's aim is to help applications handle transient failures in asynchronous actions.

API Reference and Documentation

Supported Policies

  • RetryPolicy - Sometimes you just need to try again.
  • CircuitBreakerPolicy - Don't kick services while they are down.

Installation

Using yarn:

yarn add poli-c

Or npm:

npm install --save poli-c

Then if using TypeScript or a module bundler such as webpack:

import Policy from 'poli-c'; // TypeScript
import { Policy } from 'poli-c'; // ES6

// or if not using ES6 modules
const { Policy } = require('poli-c');

RetryPolicy Examples

Basic

Retry forever

import { Policy } from 'poli-c';

...

const policy = Policy.waitAndRetryForever({ sleepDurationProvider: 1000 });

async function fetchThings() {
    const response = await fetch('https://servicethatmightfail.com/v1/things');
    const things = await response.json();
    return things;
}

...
const things = await policy.executeAsync(fetchThings);
...

Or not

const policy = Policy.waitAndRetry({ retryCount: 3, sleepDurationProvider: 1000 });

...
const things = await policy.executeAsync(fetchThings)
...

Additionally, control when the policy retries on error

const policy = Policy
    .handleError(error => error instanceof FetchError)
    .waitAndRetryForever({ sleepDurationProvider: 1000 });

async function fetchThings() {
    const response = await fetch('https://servicethatmightfail.com/v1/things');
    if (response.status !== 404 && !response.ok) {
        // if the request fails the policy will execute the action again
        throw new FetchError(response);
    }

    // but if parsing the JSON fails the action won't be retried
    const things = await response.json();
    return things;
}

...
const things = await policy.executeAsync(fetchThings);

With cancellation

The asynchronous function will be passed a cancellation token if one is provided to executeAsync. This allows for a cooperative cancellation approach (borrowed from the .NET framework). If canceled, executeAsync will currently return undefined.

import { Policy, CancellationTokenSource } from 'poli-c';

...

const policy = Policy.waitAndRetryForever({ sleepDurationProvider: 1000 });

async function fetchThings(cancellationToken) {
    const response = await cancelableFetch('https://servicethatmightfail.com/v1/things', cancellationToken);
    const things = await response.json();
    return things;
}

...

const cts = new CancellationTokenSource();
const promise = policy.executeAsync(fetchThings, cts.token);

// eventually cancel the execution
const timeout = setTimeout(() => {
    cts.cancel();
}, maxRequestTime);

const things = await promise;
if (!things) {
    // canceled
} else {
    // not canceled
    clearTimeout(timeout);
}
...

Until a valid result is obtained

import { Policy } from 'poli-c';

...

const policy = Policy
    .waitAndRetryForever({ sleepDurationProvider: 1000 })
    .untilValidResult(job => job.isComplete);

async function getJob(jobId) {
    const response = await fetch(`https://jobService.com/v1/jobs/${jobId}`);
    const job = await response.json();
    return job;
}

async function waitForJobCompletion(newJob, cancellationToken) {
    const completedJob = await policy.executeAsync(() => getJob(newJob.id), cancellationToken);
}

With exponential backoff

The library includes two backoff algorithms (full and equal jitter as described here) that are available for use.

import { Policy, backoffs } from 'poli-c';

...

const policy = Policy.waitAndRetry({ retryCount: 3, sleepDurationProvider: backoffs.fullJitter });

async function fetchThings() {
    const response = await fetch('https://servicethatmightfail.com/v1/things');
    const things = await response.json();
    return things;
}

...
const things = await policy.executeAsync(fetchThings);
...

Additionally, a custom backoff algorithm can be used:

const policy = Policy.waitAndRetry({ retryCount: 3, sleepDurationProvider: ({ retryAttempt }) => 1000 * retryAttempt });

CircuitBreakerPolicy Examples

Basic

import { Policy } from 'poli-c'; // ES6

const policy = Policy
    .handleError(error => error instanceof FetchError)
    .circuitBreaker({
        samplingDurationMs: 5000,
        failureThreshold: 0.75,
        minimumThroughput: 4,
        breakDurationMs: 5000,
    });

async function fetchThings() {
    const response = await fetch('https://servicethatmightfail.com/v1/things');
    if (response.status !== 404 && !response.ok) {
        throw new FetchError(response);
    }

    const things = await response.json();
    return things;
}

...

try {
    const things = await policy.executeAsync(fetchThings);
    return things;
} catch (e) {
    // this error may have been thrown immediately by circuit breaker if the
    // failure threshold has been met in the sampling period
    log(e);
}

Need more information about different policies and their APIs? See the API Reference!