Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
@lifeomic/attempt
Advanced tools
Library that can be used to retry functions that return promise
@lifeomic/attempt is an npm package designed to handle retry logic for asynchronous operations. It provides a simple and flexible way to retry operations that may fail due to transient issues, such as network errors or temporary unavailability of a service.
Basic Retry
This feature allows you to retry an asynchronous operation a specified number of times with a delay between attempts. In this example, the operation is retried up to 5 times with a 1-second delay between attempts.
const attempt = require('@lifeomic/attempt');
async function fetchData() {
return attempt(async () => {
// Simulate an operation that may fail
if (Math.random() < 0.5) {
throw new Error('Random failure');
}
return 'Success';
}, {
maxAttempts: 5,
delay: 1000
});
}
fetchData().then(console.log).catch(console.error);
Exponential Backoff
This feature allows you to implement exponential backoff for retrying an operation. The delay between attempts increases exponentially. In this example, the delay starts at 1 second and doubles with each subsequent attempt.
const attempt = require('@lifeomic/attempt');
async function fetchData() {
return attempt(async () => {
// Simulate an operation that may fail
if (Math.random() < 0.5) {
throw new Error('Random failure');
}
return 'Success';
}, {
maxAttempts: 5,
delay: 1000,
factor: 2
});
}
fetchData().then(console.log).catch(console.error);
Custom Retry Logic
This feature allows you to define custom logic for handling errors and deciding whether to continue retrying. In this example, a custom error handler logs the error message and continues retrying.
const attempt = require('@lifeomic/attempt');
async function fetchData() {
return attempt(async () => {
// Simulate an operation that may fail
if (Math.random() < 0.5) {
throw new Error('Random failure');
}
return 'Success';
}, {
maxAttempts: 5,
delay: 1000,
handleError: (err, context) => {
console.log(`Attempt ${context.attemptNumber} failed: ${err.message}`);
return true; // Continue retrying
}
});
}
fetchData().then(console.log).catch(console.error);
The 'retry' package provides similar functionality for retrying operations with customizable options. It supports exponential backoff, custom retry strategies, and more. Compared to @lifeomic/attempt, 'retry' offers a more extensive set of features and greater flexibility in defining retry strategies.
The 'promise-retry' package is another alternative for retrying asynchronous operations. It allows you to specify retry options such as retries, factor, minTimeout, and maxTimeout. 'promise-retry' is similar to @lifeomic/attempt but focuses specifically on promise-based operations and provides a straightforward API for retry logic.
The 'async-retry' package is designed for retrying asynchronous functions with customizable retry strategies. It supports exponential backoff, custom retry conditions, and more. 'async-retry' is comparable to @lifeomic/attempt in terms of functionality but offers additional features like custom retry conditions and more granular control over retry behavior.
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:
Using NPM:
npm i @lifeomic/attempt
Using Yarn:
yarn add @lifeomic/attempt
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.
// 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
});
// 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();
}
}
});
// 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
});
// 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
});
// 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
});
// 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
});
// 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
}
});
FAQs
Library that can be used to retry functions that return promise
The npm package @lifeomic/attempt receives a total of 130,892 weekly downloads. As such, @lifeomic/attempt popularity was classified as popular.
We found that @lifeomic/attempt demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 2 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.