├── .gitignore ├── index.js ├── package.json ├── LICENSE ├── test └── index.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # ignore most things, include some others 2 | /* 3 | /.* 4 | 5 | !bin/ 6 | !lib/ 7 | !docs/ 8 | !package.json 9 | !package-lock.json 10 | !README.md 11 | !CONTRIBUTING.md 12 | !LICENSE 13 | !CHANGELOG.md 14 | !example/ 15 | !scripts/ 16 | !tap-snapshots/ 17 | !test/ 18 | !.github/ 19 | !.travis.yml 20 | !.gitignore 21 | !.gitattributes 22 | !coverage-map.js 23 | !map.js 24 | !index.js 25 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const isFunction = f => typeof f === 'function' 2 | 3 | const retryUntil = duration => (fn, ...args) => { 4 | let threw = true 5 | const start = Date.now() 6 | const catcher = er => { 7 | const left = duration + start - Date.now() 8 | if (left > 0) 9 | return retryUntil(left)(fn, ...args) 10 | else 11 | throw er 12 | } 13 | 14 | while (true) { 15 | try { 16 | const ret = fn(...args) 17 | threw = false 18 | return (ret && isFunction(ret.then) && isFunction(ret.catch)) 19 | ? ret.catch(catcher) 20 | : ret 21 | } finally { 22 | if (threw && Date.now() - start < duration) 23 | continue 24 | } 25 | } 26 | } 27 | module.exports = retryUntil 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "retry-until", 3 | "version": "1.0.0", 4 | "files": [], 5 | "description": "Just a way to keep calling a function as long as it's throwing, for some period of time.", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/isaacs/retry-until" 9 | }, 10 | "author": "Isaac Z. Schlueter (https://izs.me)", 11 | "license": "ISC", 12 | "scripts": { 13 | "test": "tap", 14 | "snap": "tap", 15 | "preversion": "npm test", 16 | "postversion": "npm publish", 17 | "prepublishOnly": "git push origin --follow-tags" 18 | }, 19 | "tap": { 20 | "check-coverage": true 21 | }, 22 | "devDependencies": { 23 | "tap": "^14.11.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The ISC License 2 | 3 | Copyright (c) Isaac Z. Schlueter 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 15 | IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | const retryUntil = require('../') 2 | const t = require('tap') 3 | 4 | const poop = new Error('poopie') 5 | const thrower = () => { throw poop } 6 | 7 | const throwUntil = duration => { 8 | const start = Date.now() 9 | return (...args) => Date.now() - start < duration ? thrower() : args 10 | } 11 | 12 | const rejectUntil = duration => { 13 | const start = Date.now() 14 | return async (...args) => Date.now() - start < duration ? thrower() : args 15 | } 16 | 17 | t.test('retry for a while', async t => { 18 | t.strictSame(retryUntil(1000)(throwUntil(100), 1, 2, 3), [1, 2, 3]) 19 | t.strictSame(await retryUntil(1000)(rejectUntil(100), 1, 2, 3), [1, 2, 3]) 20 | }) 21 | 22 | t.test('retry but not long enough', async t => { 23 | t.throws(() => retryUntil(100)(throwUntil(1000), 1, 2, 3), poop) 24 | await t.rejects(retryUntil(100)(rejectUntil(1000), 1, 2, 3), poop) 25 | }) 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # retry-until 2 | 3 | Just a way to keep calling a function as long as it's throwing, for some 4 | period of time. 5 | 6 | If the function returns a promise, it'll keep retrying the promise as long 7 | as it keeps throwing, for the time specified. 8 | 9 | ## USAGE 10 | 11 | ```js 12 | const retryUntil = require('retry-until') 13 | const retryFor1Second = retryUntil(1000) // retry for 1 second 14 | const retryFor10Seconds = retryUntil(10000) // retry for 10 seconds 15 | 16 | // this will busy-loop calling the function as fast as it can for 17 | // up to 1s, or until it returns without throwing, whichever comes first 18 | retryFor1Second(syncFunctionThatSometimesThrowsForAWhile, 'some', 'args') 19 | 20 | // this will keep re-calling the Promise-returning function as long as it 21 | // keeps rejecting, or for 10s, whichever comes first 22 | const result = await retryFor10Seconds(asyncFunctionThatSometimesRejects, 'some', 'other', 'args') 23 | ``` 24 | 25 | ## API 26 | 27 | * `retryUntil(duration) -> Function` 28 | 29 | Returns a function that will take a function and some arguments, and then 30 | run the function repeatedly for up to `duration` milliseconds. 31 | --------------------------------------------------------------------------------