GithubHelp home page GithubHelp logo

lifeomic / attempt Goto Github PK

View Code? Open in Web Editor NEW
181.0 27.0 15.0 497 KB

A JavaScript library that makes it easier to retry functions that return a promise

License: MIT License

TypeScript 98.17% JavaScript 1.83%
retry attempt await async promise backoff exponential-backoff retry-strategies nodejs typescript

attempt's Introduction

Attempt

Greenkeeper badge Build Status Coverage Status npm version

This library exports a retry(...) function that can be used to invoke a function that returns a Promise multiple times until returned Promise is resolved or the max number of attempts is reached.

The delay between each attempt is configurable and allows multiple retry strategies.

The following features are supported:

  • Fixed delay between attempts
  • Exponential backoff
  • Exponential backoff with jitter
  • Abort retries early
  • Abort due to timeout
  • Error handler for each attempt

Installation

Using NPM:

npm i @lifeomic/attempt

Using Yarn:

yarn add @lifeomic/attempt

Usage

Node.js / CommonJS:

const retry = require('@lifeomic/attempt').retry;

ES6 / TypeScript

import { retry } from '@lifeomic/attempt';
try {
  const result = await retry(async (context) => {
    // some code that returns a promise or resolved value
  }, options);
} catch (err) {
  // If the max number of attempts was exceeded then `err`
  // will be the last error that was thrown.
  //
  // If error is due to timeout then `err.code` will be the
  // string `ATTEMPT_TIMEOUT`.
}

The options argument is optional, and when absent the default values are assigned. All times/durations are in milliseconds.

The following object shows the default options:

{
  delay: 200,
  maxAttempts: 3,
  initialDelay: 0,
  minDelay: 0,
  maxDelay: 0,
  factor: 0,
  timeout: 0,
  jitter: false,
  initialJitter: false,
  handleError: null,
  handleTimeout: null,
  beforeAttempt: null,
  calculateDelay: null
}

NOTE:

If you are using a JavaScript runtime that doesn't support modern JavaScript features such as async/await then you will need to use a transpiler such as babel to transpile the JavaScript code to your target environment.

Supported options:

  • delay: Number

    The delay between each attempt in milliseconds. You can provide a factor to have the delay grow exponentially.

    (default: 200)

  • initialDelay: Number

    The intialDelay is the amount of time to wait before making the first attempt. This option should typically be 0 since you typically want the first attempt to happen immediately.

    (default: 0)

  • maxDelay: Number

    The maxDelay option is used to set an upper bound for the delay when factor is enabled. A value of 0 can be provided if there should be no upper bound when calculating delay.

    (default: 0)

  • factor: Number

    The factor option is used to grow the delay exponentially. For example, a value of 2 will cause the delay to double each time. A value of 3 will cause the delay to triple each time. Fractional factors (e.g. 1.5) are also allowed.

    The following formula is used to calculate delay using the factor:

    delay = delay * Math.pow(factor, attemptNum)

    (default: 0)

  • maxAttempts: Number

    The maximum number of attempts or 0 if there is no limit on number of attempts.

    (default: 3)

  • timeout: Number

    A timeout in milliseconds. If timeout is non-zero then a timer is set using setTimeout. If the timeout is triggered then future attempts will be aborted.

    The handleTimeout function can be used to implement fallback functionality.

    (default: 0)

  • jitter: Boolean

    If jitter is true then the calculated delay will be a random integer value between minDelay and the calculated delay for the current iteration.

    The following formula is used to calculate delay using jitter:

    delay = Math.random() * (delay - minDelay) + minDelay

    (default: false)

  • initialJitter: Boolean

    If initialJitter is true then a jitter will also be used in the first call attempt.

    (default: false)

  • minDelay: Number

    minDelay is used to set a lower bound of delay when jitter is enabled. This property has no effect if jitter is disabled.

    (default: 0)

  • handleError: (err, context, options) => Promise<void> | void

    handleError is a function that will be invoked when an error occurs for an attempt. The first argument is the error and the second argument is the context.

  • handleTimeout: (context, options) => Promise | void

    handleTimeout is invoked if a timeout occurs when using a non-zero timeout. The handleTimeout function should return a Promise that will be the return value of the retry() function.

  • beforeAttempt: (context, options) => void

    The beforeAttempt function is invoked before each attempt. Calling context.abort() will abort the attempt and stop retrying.

  • calculateDelay: (context, options) => Number

    The calculateDelay function can be used to override the default delay calculation. Your provided function should return an integer value that is the calculated delay for a given attempt.

    Information in the provided context and options arguments should be used in the calculation.

    When calculateDelay is provided, any option that is used to calculate delay (delay, jitter, maxDelay, factor, etc.) will be ignored.

The context has the following properties:

  • attemptNum: Number

    A zero-based index of the current attempt number (0, 1, 2, etc.).

  • attemptsRemaining: Number

    The number of attempts remaining. The initial value is maxAttempts or -1 if maxAttempts is 0 (unbounded).

  • abort: () => void

    The abort function can be called when handling an error via handleError or when beforeAttempt function is invoked. The abort function should be used to prevent any further attempts in cases when an error indicates that we should not retry.

    For example, an HTTP request that returns an HTTP error code of 400 (Bad Request) should not be retried because there is a problem with the input (and retrying will not fix this). However, a request that returns 504 (Gateway Timeout) should be retried because it might be a temporary problem.

Recipes

Retry with defaults

// Try the given operation up to 3 times with a delay of 200 between
// each attempt
const result = await retry(async function() {
  // do something that returns a promise
});

Stop retrying if an error indicates that we should not retry

// Try the given operation update to 4 times. The initial delay will be 0
// and subsequent delays will be 200, 400, 800
const result = await retry(async function() {
  // do something that returns a promise
}, {
  delay: 200,
  factor: 2,
  maxAttempts: 4,
  handleError (err, context) {
    if (err.retryable === false) {
      // We should abort because error indicates that request is not retryable
      context.abort();
    }
  }
});

Retry with exponential backoff

// Try the given operation update to 4 times. The initial delay will be 0
// and subsequent delays will be 200, 400, 800 (delay doubles each time due
// to factor of `2`)
const result = await retry(async function() {
  // do something that returns a promise
}, {
  delay: 200,
  factor: 2,
  maxAttempts: 4
});

Retry with exponential backoff and max delay

// Try the given operation up to 5 times. The initial delay will be 0
// and subsequent delays will be 200, 400, 500, 500 (capped at `maxDelay`)
const result = await retry(async function() {
  // do something that returns a promise
}, {
  delay: 200,
  factor: 2,
  maxAttempts: 5,
  maxDelay: 500
});

Retry with exponential backoff, jitter, min delay, and max delay

// Try the given operation 3 times. The initial delay will be 0
// and subsequent delays will be in the following range:
// - 100 to 200
// - 100 to 400
// - 100 to 500 (capped at `maxDelay`)
// - 100 to 500 (capped at `maxDelay`)
const result = await retry(async function() {
  // do something that returns a promise
}, {
  delay: 200,
  factor: 2,
  maxAttempts: 5,
  minDelay: 100,
  maxDelay: 500,
  jitter: true
});

Stop retrying if there is a timeout

// Try the given operation up to 5 times. The initial delay will be 0
// and subsequent delays will be 200, 400, 800, 1600.
//
// If an attempt fails to complete after 1 second then the retries
// are aborted and error with `code` `ATTEMPT_TIMEOUT` is thrown.
const result = await retry(async function() {
  // do something that returns a promise
}, {
  delay: 200,
  factor: 2,
  maxAttempts: 5,
  timeout: 1000
});

Stop retrying if there is a timeout but provide a fallback

// Try the given operation up to 5 times. The initial delay will be 0
// and subsequent delays will be 200, 400, 800, 1600.
//
// If an attempt fails to complete after 1 second then the retries
// are aborted and the `handleTimeout` implements some fallback logic.
const result = await retry(async function() {
  // do something that returns a promise
}, {
  delay: 200,
  factor: 2,
  maxAttempts: 5,
  timeout: 1000,
  async handleTimeout (context) {
    // do something that returns a promise or throw your own error
  }
});

attempt's People

Contributors

austin-rausch avatar austinkelleher avatar cfbevan avatar ctdio avatar daern91 avatar dependabot[bot] avatar fossprime avatar greenkeeper[bot] avatar indigocarmen avatar joedimarzio avatar loscm avatar mdlavin avatar ndowmon avatar philidem avatar softwarewright avatar swain avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

attempt's Issues

A default factor of 0 implies the delay is ignored, according to docs

From the docs

The factor option is used to grow the delay exponentially. For example, a value of 2 will cause the delay to double each time. A value of 3 will cause the delay to triple each time. Fractional factors (e.g. 1.5) are also allowed.

The following formula is used to calculate delay using the factor:

delay = delay * Math.pow(factor, attemptNum)

(default: 0)

This implies that the delay is ignored by default. The default should probably be 1

Unclear documentation for handleError

For handleError, it's unclear from the documentation what will happen if you throw an error from within the handleError method.

Is the following enough to abort execution automatically?
handleError(context) => {
throw new Error("Stop Processing!");
}

Or would we need to add in an extra call to context.abort?:
handleError(context) => {
context.abort();
throw new Error("Stop Processing!");
}

Get time elapsed

Hello, I'm trying to debug my exponential retry logic and would like to tap into how many milliseconds have elapsed since the first attempt. Do you know of an easy way to accomplish this? I was surprised when context didn't have this information.

Unhandled promise rejection

Hi,

I am using your library in one of our projects and have discovered a bug, which leads to an unhandled promise rejection.

I've investigated the problem and found the problem. The issue arises when you use retry function with timeout and custom handle timeout and the problem is on this line:

resolve(onError(err));

which leads to an error being thrown
throw err;

The problem is that the resolve(onError(err)) is missing a catch statement. There are multiple fixes, but I think the easiest one is to change

resolve(onError(err));
from resolve(onError(err)); to onError(err).then(resolve).catch(resolve);

An in-range update of @types/node is breaking the build 🚨

The devDependency @types/node was updated from 10.11.0 to 10.11.1.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

@types/node is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • continuous-integration/travis-ci/push: The Travis CI build failed (Details).

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

Initial Delay does not seem to be respected

Running some tests, it seems as though setting initialDelay does not affect the initial start time for the delay.

Setting delay however will reduce the initial start delay.

Version 10 of node.js has been released

Version 10 of Node.js (code name Dubnium) has been released! 🎊

To see what happens to your code in Node.js 10, Greenkeeper has created a branch with the following changes:

  • Added the new Node.js version to your .travis.yml

If you’re interested in upgrading this repo to Node.js 10, you can open a PR with these changes. Please note that this issue is just intended as a friendly reminder and the PR as a possible starting point for getting your code running on Node.js 10.

More information on this issue

Greenkeeper has checked the engines key in any package.json file, the .nvmrc file, and the .travis.yml file, if present.

  • engines was only updated if it defined a single version, not a range.
  • .nvmrc was updated to Node.js 10
  • .travis.yml was only changed if there was a root-level node_js that didn’t already include Node.js 10, such as node or lts/*. In this case, the new version was appended to the list. We didn’t touch job or matrix configurations because these tend to be quite specific and complex, and it’s difficult to infer what the intentions were.

For many simpler .travis.yml configurations, this PR should suffice as-is, but depending on what you’re doing it may require additional work or may not be applicable at all. We’re also aware that you may have good reasons to not update to Node.js 10, which is why this was sent as an issue and not a pull request. Feel free to delete it without comment, I’m a humble robot and won’t feel rejected 🤖


FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.