reattempt
is a modern JavaScript library for the browser and Node.js that lets you retry asynchronous functions when they fail - because some functions deserve a second chance, or a third or maybe even several dozen or so.
- π Very lightweight: ~550 bytes minified+gzipped
- β‘οΈ Modern asynchronous JavaScript support with Promises and Async/Await
- πͺ Flexible API that covers many cases
- π Targeted for both the browser and Node.js
- β Type-safety with TypeScript and a built-in decorator
To get started, add reattempt
to your project:
npm i --save-dev reattempt
When an async
function (or a function that returns a Promise
) is passed to Reattempt.run
, the function will be called immediately. If the functions reject with an error, Reattempt.run
will retry calling that function. The function will be retried until it resolves, or until the maximum retries count is reached, whichever comes first.
import Reattempt from 'reattempt';
async function doSomethingAsync() {
// doing async operation that may throw
return result;
}
async function main() {
try {
const result = await Reattempt.run({ times: 3 }, doSomethingAsync);
} catch (error) {
// an error is thrown if the function rejects with an error after
// exhausting all attempts
}
}
Reattempt also works with functions following the error-first callbacks pattern. When working with these functions, instead of passing an async
or Promise
based function, pass a function with a single argument called done
. Use this argument as the error-first callback of your function.
The function will be retried until it returns a value without an error, or until the maximum retries count is reached, whichever comes first.
import fs from 'fs';
import Reattempt from 'reattempt';
async function main() {
try {
const data = await Reattempt.run({ times: 3 }, done => {
fs.readFile('./path/to/file', 'utf8', done);
});
} catch (error) {
// an error is thrown if the function rejects with an error after
// exhausting all attempts
}
}
Similar to working with Node.js Error-First Callbacks, the done
callback can be used to reattempt any asynchronous function with custom callback interface. For example, some APIs expects an onSuccess
and onError
callbacks.
The properties done.resolve
and done.reject
can be used to hook into any custom interface and perform reattempts as needed.
function doSomething(onSuccess, onError) {
// some async operations
}
async function main() {
try {
const data = await Reattempt.run({ times: 3 }, done => {
doSomething(done.resolve, done.reject);
});
} catch (error) {
// an error is thrown if the function rejects with an error after
// exhausting all attempts
}
}
There are cases when you need to intercept an attempt call. It's possible to control the reattempt flow, by providing the onError
option. This option allows you to intercept each attempt and control the reattempt flow.
import Reattempt from 'reattempt';
async function doSomething() {
// some async operations
}
function handleError(
error /* the error object that the function rejected with */,
done /* resolves the function call with a custom value */,
abort /* bail out of remaining attempts and rejects with current error */,
) {
if (shouldAbortRemainingAttempts) {
abort();
} else if (shouldSkipAttemptsAndResolve) {
done(defaultValue);
}
}
async function main() {
try {
const result = await Reattempt.run(
{ times: 10, onError: handleError },
doSomething,
);
} catch (error) {
// ...
}
}
Reattempt also comes as a decorator that can be imported from reattempt/decorator
.
import Reattempt from 'reattempt/decorator';
class Group {
@Reattempt({ times: 3, delay: 5000 })
private async getUserIds() {
const user = await fakeAPI.getUsers(this.id); // could throw!
return users.map(user => user.id);
}
public async doSomething() {
try {
const result = await this.getUserIds();
} catch (error) {
// Only throws after failing 3 attempts with 5 seconds in between
}
}
}
Reattempt can infer types of async and Promise-based functions automatically.
However, when working with error-first callbacks, you can enforce type safety by passing a type argument informing Reattempt about the list of success arguments the original function could potentially provide.
Reattempt
.run<[string, string]>({ times: 3 }, done => {
childProcess.exec('cat *.md | wc -w', attempt);
})
// resolves with an array of success type-safe arguments
.then(([stdout, stderr]) => stdout.trim())
.catch(error => /* ... */);
Runs and reattempt the provided callback. If the callback fails, it will be reattempted until it resolves, or until the maximum retries count options.times
is reached, whichever comes first.
Returns a Promise
that resolves with the result of the provided function, and rejects with the same error it could reject with.
All Reattempt methods accept an options object as the first argument with the following properties:
The number of times a function can be reattempted.
If this property is not provided Reattempt will perform the provided function once without any additional reattempts on failure.
The duration in milliseconds between each attempt. Defaults to 0
.
If this property is not provided Reattempt will perform a reattempt as soon as the function fails.
A callback that fires on each attempt after receiving an error. It allows you to intercept an attempt and gives you access to the error object. It passes the following parameters:
error: any
: the error that the function rejected withdone(value: any): void
: a function that allows you to skip remaining reattempts and resolve the attempted function with the value provided.abort(): void
: a function allowing you to bail out of remaining attempts and rejects the attempted function immediately.
All Reattempt methods take a function as the second argument.
This function will be reattempted on failure and can be one of three forms:
- An
async
function.
Reattempt.run({ times: 2 }, async () => {
// ...
});
- A function that returns a
Promise
Reattempt.run({ times: 2 }, () => {
return new Promise((resolve, reject) => {
//...
});
});
- A non-
async
, non-Promise
function that wraps functions with error-first-callbacks
Reattempt.run({ times: 2 }, done => {
fs.readFile('path/to/file', 'utf-8', done);
});
If you are reattempting a non-async
function (or a function that does not return a Promise
), pass a callback function with one argument done
.
This argument controls the reattempt flow and can be used in one of two ways:
- As an error-first callback that you can pass to any function such as most Node.js APIs
- As a hook to custom interfaces that expects success and error callbacks by utilizing the two properties
done.resolve
anddone.reject
.
MIT