dread
When you just know things will fail
Usage
const dread = require('dread');
The most basic, batteries-included example:
// this will retry 10 times with fully randomized and truncated exponential backoff
dread(function() {
// do stuff
});
You can also provide options to override retry logic:
dread({ attempts: 3 }, function() {
// same as above, but with only 3 attempts just for this task
});
You can also provide options as the only argument, which returns a reconfigured instance of dread
:
const retry = dread({ attempts: 3 });
retry(function() {
// this is a task, 3 attempts
})
retry(function() {
// this is a different task, still 3 attempts
});
dread(function() {
// this still uses the default configuratiopn
});
The order that you provide arguments does not matter - you can call dread(task, options)
or dread(options, task)
.
Options
The default configuration is equivalent to:
dread({
attempts: 10,
condition: dread.prop('retryable'),
backoff: dread.exp(),
timeout: undefined,
});
attempts
The number of attempts inclusive of the first one
// fewer attempts than default
dread({ attempts: 5 });
You can also call it retries
instead, but note this is not inclusive. So dread({ attempts: 5})
is the same as dread({ retries: 4 })
condition
A function Error => Boolean
that determines whether or not to retry a failed attempt.
You can provide any function you want, including async.
dread
exports helpers:
dread.prop(name)
rery iferr.prop
is truthydread.code(String)
retry iferr.code === String
dread.is(Class)
retry iferr instanceof Class
dread.always()
always retry every error
backoff
A function (Integer, Error) => Integer
that returns the amount of time to wait before the next attempt.
You can provide any function you want, including async. The Error
is provided in case you want to have different retry periods for different error cases, or, as in the case of aws-sdk
, it already provides a backoff duration on its errors.
dread
exports a helper:
// this is the default - a fully randomized, truncated exponential value
dread.exp({
base: Number(100),
factor: Number(2),
limit: Number(10000),
jitter: JitterType.FULL
});
jitter
may be one of:
JitterType.NONE
the full backoff value is usedJitterType.FULL
a randomized value between 0 and the full backoffJitterType.HALF
a randomized valued between full backoff / 2 and full backoff
timeout
dread
supports a timeout for the entire operation
const retry = dread({ timeout: 10000 });
// the operation will timeout after 10 seconds
// regardless of the number of attempts or how long the current attempt takes
retry(function() {
// ...
});
Attempt Object
Your task is invoked with an attempt
object as the only argument.
dread(attempt => {
console.log('attempt number', attempt.number);
attempt.cancel('things are going haywire!'); // will retry
});
number
number
is the number of the current attempt (1, 2, 3, etc.) - it can also be accessed via the property attempt.attempt
, which seems like a strange name, but it's so that you can destructure it like:
dread(({ attempt }) => {
logger.info({ attempt }, 'trying something'); // { attempt: 1, msg: 'trying something' }
});
timeout(value)
// this attempt will timeout after 1 second, inclusive of the backoff delay
attempt.timeout(5000);
// you could also set a timeout based on the attempt nuber
attempt.timeout(attempt.number * 2000);
Setting a timeout after an attempt has succeeded has no effect;
cancel(reason)
You can cancel an attempt manually by calling attempt.cancel(reason)
optionally providing either an Error or a String, which will be used as the error message.
dread(attempt => {
// will reject with new Error('Cancelled')
attempt.cancel();
// will reject with new Error('Try again')
attempt.cancel('Try again');
// will reject with MyCustomError
attempt.cancel(new MyCustomError());
});
Calling cancel after an attempt has succeeded has no effect.