README
sisyphus-retry
Lightweight library for retrying asynchronous tasks with fluent API.
Based on this reddit comment.
Installation
Formats
sisyphus-retry
distributes in a number of formats.
Format | Minified? | Main use case | |
---|---|---|---|
source | ES2015 module | :x: | Bundler |
es | ES2015 module | :heavy_check_mark: | Modern browser |
cjs | CommonJS | :heavy_check_mark: | Node |
global | Global var export | :heavy_check_mark: | Browser |
Source
For being built by a ES2015-aware bundler since those work better using unminfied source. A bundler that respects the "module" package.json field (e.g. Webpack 2+, rollup) will use this distributable.
ES2015 module
Minified ES2015 module. This is for importing in the modern browsers that support <script type=module>
. See the Broswer section.
CommonJS
Mainly for require
from NodeJS, which uses this distributable because of the package.json "main" field.
Global variable export
This exports to the sisyphus
global variable. Mainly for being used from a broswer via <script>
.
npm
$ npm install sisyphus-retry
# or
$ yarn add sisyphus-retry
# or
$ pnpm install sisyphus-retry
The npm package includes all formats
Modern browsers
If you only target modern browsers that support ES2015 modules.
<script type="module">
import sisyphus from '//unpkg.com/sisyphus-retry/dist/sisyphus.es.js'
// Your code
</script>
<!-- or, for a specific version -->
<script type="module">
import sisyphus from '//unpkg.com/sisyphus-retry@0.2.0/dist/sisyphus.es.js'
// Your code
</script>
Browsers
If you target browsers that don't support ES2015 modules, either use the global variable bundle
<script src="//unpkg.com/sisyphus-retry"></script>
<!-- or, for a specific version -->
<script src="//unpkg.com/sisyphus-retry@0.2.0"></script>
or use a fallback
<script type="module">
import sisyphus from '//unpkg.com/sisyphus-retry/dist/sisyphus.es.js'
window.sisyphus = sisyphus
</script>
<script src="//unpkg.com/sisyphus-retry" nomodule></script>
<script>
// Now sisyphus is defined as a global
</script>
Like above, you can use a specific version by adding specifiers
Usage
Example
import sisyphus from 'sisyphus-retry'
import exponentially from 'sisyphus-retry/dist/wait/exponentially.es.js'
// A task that never succeeds
const pushBoulder = () =>
Promise.reject(new Error("Boulder rolls down again!"))
// A network request using axios
const fetchData = () => axios.get('/url/to/data')
sisyphus()
.triesTo(pushBoulder) // Sets the task
.indefinitely() // Max # of attempts = Infinity
.every(100).ms // Wait 100 ms in between attempts
.now() // Go
sisyphus()
.triesTo(fetchData) // Sets the task
.trying(5).times // Max # of attempts = 5
.backingOff(exponentially() // Exponential backoff
.startingAt(100).ms
.withFactor(2))
.now() // Returns promise:
.then(function(response) {
// ...
})
.catch(function(err) {
// ...
})
Introduction
Tasks
In Sisyphus, an async task (or simply task) is a function that, when called with no arguments, returns a promise. A call of a task is known as an attempt. If and when the promise fulfills with some value, the task is said to succeed with that value. On the other hand, if and when the promise rejects with some reason, the task is said to fail with that reason.
A Sisyphus instance attempts the given task until an attempt succeeds or until a configured maximum number of attempts is reached.
API
Creation
sisyphus()
Creates and returns a Sisyphus instance. Note that due to the fluent api, it is usually unnecessary to assign the instance to a variable.
Properties
Fleunt no-ops
The .ms
, .time
and .times
properties are references back to the instance. They are provided simply for fluency.
Instance methods
All instance methods, except otherwise sepcified, returns the instance.
, .triesTo(task).tries(task)
Sets the task to be run by the Sisyphus instance. The default task (the one set on instance creation) is one that succeeds with undefined
immediately.
.trying(num)
Sets the maximum number of attempts. If num is zero or negative, behavior is unspecified. The default maximum is infinite (no limit).
, .once().twice()
, .thrice()
, .indefinitely()
, .infinte()
These convenience methods set the maximum number of attempts to the corresponding value.
, .waiting(fn).backingOff(fn)
This sets a function to calculate a wait time between attempts. The function is called with the attempt number that just failed, with the initial attempt being the 0th attempt, and should return the number of milliseconds to wait before the next attempt.
// Constant wait time
sisyphus().waiting(n => 1000)
// Linear backoff
// Note that after the initial attempt fails, the next one will immediately start
sisyphus().waiting(n => 1000 * n)
// The world is your oyster
sisyphus().waiting(n => Math.floor(1000 * n * Math.random()))
.every(ms)
A convenience method for a constant wait time, given in milliseconds
, .now().$()
Runs the task with retries. Returns a promise.
Since the .now
takes no arguments (although it requires the Sisyphus instance as this
) and returns a promise, it can itself be thought of as a task -- one that succeeds with the same value if and when any attempt of the inner task succeeds.
If the Sisyphus instance has no maximum attempt limit, then the outer task never fails. If the Sisyphus instance has a maximum attempt limit, then the outer task fails with the same reason if and when the last attempt of the inner task fails.
The high-level algorithm of the outer task is:
- Attempt the inner task
- If attempt succeeds, the outer task succeeds with the same value as this attempt. END
- If attempt fails, and the number of attempts finished >= configured maximum, the outer task fails with the same reason as this attempt. END
- Otherwise, call the wait function to get the wait time
- Wait that many milliseconds
- Go back to step 1
Note on concurrency: Between calling .now
/.$
and the return promise settles, do not change any configuration of that Sisyphus instance or call .now
/.$
again. The behavior after doing so is unspecified.
Wait time addons
Sisyphus includes some fluently-configurable wait functions.
Linear Backoff | Exponential Backoff | |
---|---|---|
source | src/wait/linearly.js |
src/wait/exponentially.js |
es | dist/wait/linearly.es.js |
dist/wait/exponentially.es.js |
cjs | dist/wait/linearly.cjs.js |
dist/wait/exponentially.cjs.js |
extend | dist/wait/linearly.js |
dist/wait/exponentially.js |
For the npm package, the above paths are relative to the package root. For UNPKG, the paths are relative to //unpkg.com/sisyphus-retry
.
The "extend" format is to be used with the "global" main format. They find a sisyphus
global variable and exports to sisyphus.wait.linear
and sisyphus.wait.exponentially
respectively.
Linear Backoff
linearly([start, [increment]])
Returns a linear backoff function. The wait time after the initial attempt is start
milliseconds. Each subsequent attempt has wait time increment
milliseconds more than the last.
start
and increment
both defaults to 0.
Properties
The .ms
property of a linear backoff function is a reference to the function itself
Instance methods
The linear backoff functions also have .startingAt(ms)
and .incrementingBy(ms)
methods to reconfigure the linear backoff fluently.
Exponential Backoff
exponentially([start, [factor]])
Returns an exponential backoff function. The wait time after the initial attempt is start
milliseconds. Each subsequent attempt has wait time equal to the last one multiplied by factor
.
start
and factor
defaults to 0 and 1, respectively.
Properties
The .ms
property of an exponential backoff function is a reference to the function itself
Instance methods
The exponential backoff functions also have .startingAt(ms)
and .withFactor(factor)
methods to reconfigure the exponential backoff fluently.
Sisyphus?
Sisyphus is a greek mythology figure who is punished by Zeus to roll a boulder up a hill, but the boulder is enchanted to roll down every time it nears the top, trapping Sisyphus indefinitely.
License
MIT