Zero-dependency promise retries. Exponential back-off by default, and highly configurable.
const { yourPromiseReturningFunction } = require('./your-module');
const yourFunctionWithRetry = require('but-you-promised')(yourPromiseReturningFunction);
Now use yourFunctionWithRetry
exactly how you would use yourPromiseReturningFunction
, except now when you call it, up to 5 attempts will be made if the promise rejects.
butYouPromised(yourFunction[, options])
yourFunction
required function
A function that returns a promise. A common usecase would be a function that makes a network request when called.
options
optional object
An object that can be passed-in to override default settings.
-
giveUpAfterAttempt
optional integer, the default is 5An integer that sets the maximum number of times
yourFunction
will be called before rejecting. The number set here will only ever be reached if your function’s promise consistently rejects. -
backOffSeedDelayInMs
optional integer, the default is 1000An integer that sets the back off delayed seed in milliseconds. This can be used to set the delay when omitting the default
createBackOffFunction
const wrappedFunction = butYouPromised(yourFunction, { backOffSeedDelayInMs: 2000, giveUpAfterAttempt: 10 });
-
createBackOffFunction
optional function, the default creates an exponential delay functionA function used internally to create a back-off strategy between attempts, when first wrapping
yourFunction
. When called,createBackOffFunction
should return a new function (let’s call it Y here for clarity). Y should return an integer and will be called after each failed attempt, in order to determine the minimum number of milliseconds to wait before another attempt (unlessgiveUpAfterAttempt
has been reached). Y will be called internally with one parameter, which is a count of how many attempts have been made so far. This gives you flexibility to define how your subsequent attempts are made.createBackOffFunction: ({ seedDelayInMs }) => { return attemptsSoFar => attemptsSoFar * seedDelayInMs; }
createBackOffFunction: () => () => 0
-
onFulfilled
optional function, the default is a no-op function (but passes the result through)A function that will be called internally if
yourFunction
’s promise is fulfilled. This is useful if you want to override what is deemed a successful scenario, such as a network request that returns a 500 response.onFulfilled: (result = {}) => { if (result.status > 500) { throw new Error(`Received a server error ${result.status}`); } return result; }
-
onRejected
optional function, the default is a no-op function (well, kinda—it rethrows the received error)A function that will be called internally every time
yourFunction
’s promise is rejected (if at all). This is useful if you want to override what is deemed a failure scenario, or if you want to log attempts.Note that you should rethrow the error passed into this function if you want to trigger another attempt (unless the
giveUpAfterAttempt
number has been reached).onRejected: (err) => { console.error(`Failed to do the thing. Got this error message: ${err.message}`); throw err; // replay error to trigger subsequent attempts }
onRejected: (err) => { if (err.status >= 500) { // If the error is not expected to change with multiple attempts (in this case if an HTTP network response code is, say, 404 (not found), subsequent attempts are not helpful) throw err; // replay error to trigger subsequent attempts } }
Always returns a function
that will return a promise when called.
Run npm install but-you-promised
in your terminal.
Wrap your promise-returning function like this:
const { yourFunction } = require('./your-module');
const wrappedFunction = require('but-you-promised')(yourFunction);
Before:
yourFunction('yourParameter1', 'yourParameter2', 'yourParameter3')
.then(result => console.log('Result:', result))
.catch(() => {
console.log('Failed after only 1 attempt');
});
After:
wrappedFunction('yourParameter1', 'yourParameter2', 'yourParameter3')
.then(result => console.log('Result:', result))
.catch(() => {
console.log('Failed after a maximum of 5 attempts');
});
Before:
(async () => {
try {
const result = await yourFunction('yourParameter1', 'yourParameter2', 'yourParameter3');
console.log('Result:', result);
} catch (err) {
console.log('Failed after only 1 attempt');
}
}());
After:
(async () => {
try {
const result = await wrappedFunction('yourParameter1', 'yourParameter2', 'yourParameter3');
console.log('Result:', result);
} catch (err) {
console.log('Failed after a maximum of 5 attempts');
}
}());
- It’s worth making sure that
yourFunction
doesn’t already make multiple attempts if a promise rejects (for example if you’re wrapping a third-party function), else you may make more network calls than you’re intending! - If you’re using this software as part of an ongoing web request, consider using a custom back-off function (which delays exponentially by default), or reducing the default number of attempts (5), otherwise the original request may time out.
Licensed under the Lesser General Public License (LGPL-3.0).