reattempt
v0.1.1
Published
Give your functions another chance
Downloads
759
Maintainers
Readme
reattempt
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.
Highlights
- 🚀 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
Getting Started
To get started, add reattempt
to your project:
npm i --save-dev reattempt
Usage
Asynchronous Promise-Based Functions
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
}
}
Node.js Error-First Callbacks
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
}
}
Custom Interface Functions
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
}
}
Intercepting 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) {
// ...
}
}
Working with TypeScript
Reattempt As A Decorator
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
}
}
}
Type Safe Callbacks
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 => /* ... */);
API
Methods
run(options: Options, callback: Callback): Promise
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.
Reattempt Options
All Reattempt methods accept an options object as the first argument with the following properties:
times: number
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.
delay?: number
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.
onError?(error, done, abort): void
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.
Reattempt Callback
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);
});
The done
Callback
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
.
License
MIT