Package Exports
- @lifeomic/attempt
- @lifeomic/attempt/dist/es6/src/index.js
- @lifeomic/attempt/dist/src/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@lifeomic/attempt) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
Attempt
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/attemptUsing Yarn:
yarn add @lifeomic/attemptUsage
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:NumberThe delay between each attempt in milliseconds. You can provide a
factorto have thedelaygrow exponentially.(default:
200)initialDelay:NumberThe
intialDelayis the amount of time to wait before making the first attempt. This option should typically be0since you typically want the first attempt to happen immediately.(default:
0)maxDelay:NumberThe
maxDelayoption is used to set an upper bound for the delay whenfactoris enabled. A value of0can be provided if there should be no upper bound when calculating delay.(default:
0)factor:NumberThe
factoroption is used to grow thedelayexponentially. For example, a value of2will cause the delay to double each time. A value of3will 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:NumberThe maximum number of attempts or
0if there is no limit on number of attempts.(default:
3)timeout:NumberA timeout in milliseconds. If
timeoutis non-zero then a timer is set usingsetTimeout. If the timeout is triggered then future attempts will be aborted.The
handleTimeoutfunction can be used to implement fallback functionality.(default:
0)jitter:BooleanIf
jitteristruethen the calculated delay will be a random integer value betweenminDelayand 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:BooleanIf
initialJitteristruethen ajitterwill also be used in the first call attempt.(default:
false)minDelay:NumberminDelayis used to set a lower bound of delay whenjitteris enabled. This property has no effect ifjitteris disabled.(default:
0)handleError:(err, context, options) => Promise<void> | voidhandleErroris 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 | voidhandleTimeoutis invoked if a timeout occurs when using a non-zerotimeout. ThehandleTimeoutfunction should return aPromisethat will be the return value of theretry()function.beforeAttempt:(context, options) => voidThe
beforeAttemptfunction is invoked before each attempt. Callingcontext.abort()will abort the attempt and stop retrying.calculateDelay:(context, options) => NumberThe
calculateDelayfunction 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
contextandoptionsarguments should be used in the calculation.When
calculateDelayis provided, any option that is used to calculate delay (delay,jitter,maxDelay,factor, etc.) will be ignored.
The context has the following properties:
attemptNum:NumberA zero-based index of the current attempt number (
0,1,2, etc.).attemptsRemaining:NumberThe number of attempts remaining. The initial value is
maxAttemptsor-1ifmaxAttemptsis0(unbounded).abort:() => voidThe
abortfunction can be called when handling an error viahandleErroror whenbeforeAttemptfunction 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 returns504(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
}
});