A JavaScript library that makes it easier to retry functions that return a promise
Installations
npm install @lifeomic/attempt
Developer Guide
Typescript
Yes
Module System
CommonJS
Node Version
14.21.3
NPM Version
8.12.1
Releases
Contributors
Unable to fetch Contributors
Languages
TypeScript (98.17%)
JavaScript (1.83%)
Developer
lifeomic
Download Statistics
Total Downloads
20,544,143
Last Day
9,503
Last Week
56,689
Last Month
547,693
Last Year
6,494,595
GitHub Statistics
189 Stars
139 Commits
17 Forks
27 Watching
3 Branches
28 Contributors
Bundle Size
2.66 kB
Minified
1.05 kB
Minified + Gzipped
Package Meta Information
Latest Version
3.1.0
Package Id
@lifeomic/attempt@3.1.0
Unpacked Size
32.74 kB
Size
6.55 kB
File Count
7
NPM Version
8.12.1
Node Version
14.21.3
Publised On
21 Mar 2024
Total Downloads
Cumulative downloads
Total Downloads
20,544,143
Last day
-10.8%
9,503
Compared to previous day
Last week
-45.5%
56,689
Compared to previous week
Last month
-16.6%
547,693
Compared to previous month
Last year
21.4%
6,494,595
Compared to previous year
Daily Downloads
Weekly Downloads
Monthly Downloads
Yearly Downloads
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:
1npm i @lifeomic/attempt
Using Yarn:
1yarn add @lifeomic/attempt
Usage
Node.js / CommonJS:
1const retry = require('@lifeomic/attempt').retry;
ES6 / TypeScript
1import { retry } from '@lifeomic/attempt';
1try { 2 const result = await retry(async (context) => { 3 // some code that returns a promise or resolved value 4 }, options); 5} catch (err) { 6 // If the max number of attempts was exceeded then `err` 7 // will be the last error that was thrown. 8 // 9 // If error is due to timeout then `err.code` will be the 10 // string `ATTEMPT_TIMEOUT`. 11}
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:
1{ 2 delay: 200, 3 maxAttempts: 3, 4 initialDelay: 0, 5 minDelay: 0, 6 maxDelay: 0, 7 factor: 0, 8 timeout: 0, 9 jitter: false, 10 initialJitter: false, 11 handleError: null, 12 handleTimeout: null, 13 beforeAttempt: null, 14 calculateDelay: null 15}
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 thedelay
grow exponentially.(default:
200
) -
initialDelay
:Number
The
intialDelay
is the amount of time to wait before making the first attempt. This option should typically be0
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 whenfactor
is enabled. A value of0
can be provided if there should be no upper bound when calculating delay.(default:
0
) -
factor
:Number
The
factor
option is used to grow thedelay
exponentially. For example, a value of2
will cause the delay to double each time. A value of3
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 usingsetTimeout
. 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
istrue
then the calculated delay will be a random integer value betweenminDelay
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
istrue
then ajitter
will also be used in the first call attempt.(default:
false
) -
minDelay
:Number
minDelay
is used to set a lower bound of delay whenjitter
is enabled. This property has no effect ifjitter
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-zerotimeout
. ThehandleTimeout
function should return aPromise
that will be the return value of theretry()
function. -
beforeAttempt
:(context, options) => void
The
beforeAttempt
function is invoked before each attempt. Callingcontext.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
andoptions
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
ifmaxAttempts
is0
(unbounded). -
abort
:() => void
The
abort
function can be called when handling an error viahandleError
or whenbeforeAttempt
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 returns504
(Gateway Timeout) should be retried because it might be a temporary problem.
Recipes
Retry with defaults
1// Try the given operation up to 3 times with a delay of 200 between 2// each attempt 3const result = await retry(async function() { 4 // do something that returns a promise 5});
Stop retrying if an error indicates that we should not retry
1// Try the given operation update to 4 times. The initial delay will be 0 2// and subsequent delays will be 200, 400, 800 3const result = await retry(async function() { 4 // do something that returns a promise 5}, { 6 delay: 200, 7 factor: 2, 8 maxAttempts: 4, 9 handleError (err, context) { 10 if (err.retryable === false) { 11 // We should abort because error indicates that request is not retryable 12 context.abort(); 13 } 14 } 15});
Retry with exponential backoff
1// Try the given operation update to 4 times. The initial delay will be 0 2// and subsequent delays will be 200, 400, 800 (delay doubles each time due 3// to factor of `2`) 4const result = await retry(async function() { 5 // do something that returns a promise 6}, { 7 delay: 200, 8 factor: 2, 9 maxAttempts: 4 10});
Retry with exponential backoff and max delay
1// Try the given operation up to 5 times. The initial delay will be 0 2// and subsequent delays will be 200, 400, 500, 500 (capped at `maxDelay`) 3const result = await retry(async function() { 4 // do something that returns a promise 5}, { 6 delay: 200, 7 factor: 2, 8 maxAttempts: 5, 9 maxDelay: 500 10});
Retry with exponential backoff, jitter, min delay, and max delay
1// Try the given operation 3 times. The initial delay will be 0 2// and subsequent delays will be in the following range: 3// - 100 to 200 4// - 100 to 400 5// - 100 to 500 (capped at `maxDelay`) 6// - 100 to 500 (capped at `maxDelay`) 7const result = await retry(async function() { 8 // do something that returns a promise 9}, { 10 delay: 200, 11 factor: 2, 12 maxAttempts: 5, 13 minDelay: 100, 14 maxDelay: 500, 15 jitter: true 16});
Stop retrying if there is a timeout
1// Try the given operation up to 5 times. The initial delay will be 0 2// and subsequent delays will be 200, 400, 800, 1600. 3// 4// If an attempt fails to complete after 1 second then the retries 5// are aborted and error with `code` `ATTEMPT_TIMEOUT` is thrown. 6const result = await retry(async function() { 7 // do something that returns a promise 8}, { 9 delay: 200, 10 factor: 2, 11 maxAttempts: 5, 12 timeout: 1000 13});
Stop retrying if there is a timeout but provide a fallback
1// Try the given operation up to 5 times. The initial delay will be 0 2// and subsequent delays will be 200, 400, 800, 1600. 3// 4// If an attempt fails to complete after 1 second then the retries 5// are aborted and the `handleTimeout` implements some fallback logic. 6const result = await retry(async function() { 7 // do something that returns a promise 8}, { 9 delay: 200, 10 factor: 2, 11 maxAttempts: 5, 12 timeout: 1000, 13 async handleTimeout (context) { 14 // do something that returns a promise or throw your own error 15 } 16});
No vulnerabilities found.
Reason
all changesets reviewed
Reason
no dangerous workflow patterns detected
Reason
no binaries found in the repo
Reason
license file detected
Details
- Info: project has a license file: LICENSE:0
- Info: FSF or OSI recognized license: MIT License: LICENSE:0
Reason
packaging workflow detected
Details
- Info: Project packages its releases by way of GitHub Actions.: .github/workflows/release.yml:9
Reason
SAST tool detected but not run on all commits
Details
- Info: SAST configuration detected: CodeQL
- Warn: 4 commits out of 30 are checked with a SAST tool
Reason
0 commit(s) and 1 issue activity found in the last 90 days -- score normalized to 0
Reason
dependency not pinned by hash detected -- score normalized to 0
Details
- Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/code-scanning-2022-06-29.yml:41: update your workflow using https://app.stepsecurity.io/secureworkflow/lifeomic/attempt/code-scanning-2022-06-29.yml/master?enable=pin
- Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/code-scanning-2022-06-29.yml:49: update your workflow using https://app.stepsecurity.io/secureworkflow/lifeomic/attempt/code-scanning-2022-06-29.yml/master?enable=pin
- Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/code-scanning-2022-06-29.yml:57: update your workflow using https://app.stepsecurity.io/secureworkflow/lifeomic/attempt/code-scanning-2022-06-29.yml/master?enable=pin
- Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/code-scanning-2022-06-29.yml:60: update your workflow using https://app.stepsecurity.io/secureworkflow/lifeomic/attempt/code-scanning-2022-06-29.yml/master?enable=pin
- Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/pr-branch-build.yml:9: update your workflow using https://app.stepsecurity.io/secureworkflow/lifeomic/attempt/pr-branch-build.yml/master?enable=pin
- Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/pr-branch-build.yml:10: update your workflow using https://app.stepsecurity.io/secureworkflow/lifeomic/attempt/pr-branch-build.yml/master?enable=pin
- Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yml:13: update your workflow using https://app.stepsecurity.io/secureworkflow/lifeomic/attempt/release.yml/master?enable=pin
- Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yml:14: update your workflow using https://app.stepsecurity.io/secureworkflow/lifeomic/attempt/release.yml/master?enable=pin
- Info: 0 out of 8 GitHub-owned GitHubAction dependencies pinned
Reason
detected GitHub workflow tokens with excessive permissions
Details
- Warn: no topLevel permission defined: .github/workflows/code-scanning-2022-06-29.yml:1
- Warn: no topLevel permission defined: .github/workflows/pr-branch-build.yml:1
- Warn: no topLevel permission defined: .github/workflows/release.yml:1
- Info: no jobLevel write permissions found
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
project is not fuzzed
Details
- Warn: no fuzzer integrations found
Reason
security policy file not detected
Details
- Warn: no security policy file detected
- Warn: no security file to analyze
- Warn: no security file to analyze
- Warn: no security file to analyze
Reason
38 existing vulnerabilities detected
Details
- Warn: Project is vulnerable to: GHSA-67hx-6x53-jw92
- Warn: Project is vulnerable to: GHSA-v88g-cgmw-v5xw
- Warn: Project is vulnerable to: GHSA-93q8-gq69-wqmw
- Warn: Project is vulnerable to: GHSA-pp7h-53gx-mx7r
- Warn: Project is vulnerable to: GHSA-cwfw-4gq5-mrqx
- Warn: Project is vulnerable to: GHSA-g95f-p29q-9xw4
- Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg
- Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275
- Warn: Project is vulnerable to: GHSA-rq8g-5pc5-wrhr
- Warn: Project is vulnerable to: GHSA-hr2v-3952-633q
- Warn: Project is vulnerable to: GHSA-h6ch-v84p-w6p9
- Warn: Project is vulnerable to: GHSA-pfrx-2q88-qq97
- Warn: Project is vulnerable to: GHSA-jcpv-g9rr-qxrc
- Warn: Project is vulnerable to: GHSA-44pw-h2cw-w3vq
- Warn: Project is vulnerable to: GHSA-jp4x-w63m-7wgm
- Warn: Project is vulnerable to: GHSA-c429-5p7v-vgjp
- Warn: Project is vulnerable to: GHSA-2p57-rm9w-gvfp
- Warn: Project is vulnerable to: GHSA-2pr6-76vf-7546
- Warn: Project is vulnerable to: GHSA-8j8c-7jfh-h6hx
- Warn: Project is vulnerable to: GHSA-896r-f27r-55mw
- Warn: Project is vulnerable to: GHSA-9c47-m6qq-7p4h
- Warn: Project is vulnerable to: GHSA-6c8f-qphg-qjgp
- Warn: Project is vulnerable to: GHSA-4xcv-9jjx-gfj3
- Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv
- Warn: Project is vulnerable to: GHSA-wrvr-8mpx-r7pp
- Warn: Project is vulnerable to: GHSA-vh95-rmgr-6w4m / GHSA-xvch-5gv4-984h
- Warn: Project is vulnerable to: GHSA-gqgv-6jq5-jjj9
- Warn: Project is vulnerable to: GHSA-hrpp-h998-j3pp
- Warn: Project is vulnerable to: GHSA-6g33-f262-xjp4
- Warn: Project is vulnerable to: GHSA-7xfp-9c55-5vqj
- Warn: Project is vulnerable to: GHSA-p8p7-x288-28g6
- Warn: Project is vulnerable to: GHSA-4g88-fppr-53pp
- Warn: Project is vulnerable to: GHSA-4jqc-8m5r-9rpr
- Warn: Project is vulnerable to: GHSA-mf6x-7mm4-x2g7
- Warn: Project is vulnerable to: GHSA-72xf-g2v4-qvf3
- Warn: Project is vulnerable to: GHSA-7p7h-4mm5-852v
- Warn: Project is vulnerable to: GHSA-xc7v-wxcw-j472
- Warn: Project is vulnerable to: GHSA-p9pc-299p-vxgp
Score
4.6
/10
Last Scanned on 2025-01-06
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More