/**
* check a thing is a promise and make it so if not
* @param {*} thing the thing
* @param {Promise}
*/
ns.promify = function(thing) {
// is it already a promise
var isPromise = !!thing &&
(typeof thing === 'object' || typeof thing === 'function') &&
typeof thing.then === 'function';
// is is a function
var isFunction = !isPromise && !!thing && typeof thing === 'function';
// create a promise of it .. will also make a promise out of a value
return Promise.resolve(isFunction ? thing() : thing);
};
/**
* a handly timer
* @param {*} [packet] something to pass through when time is up
* @param {number} ms number of milliseconds to wait
* @return {Promise} when over
*/
ns.handyTimer = function(ms, packet) {
// wait some time then resolve
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(packet);
}, ms);
});
};
/**
* expbackoff
* @param {function | Promise} action what to do
* @param {function} doRetry whether to retry
* @param {object} [options]
* @param {number} [options.maxPasses=5] how many times to try
* @param {number} [options.waitTime=500] how long to wait on failure
* @param {number} [options.passes=0] how many times we've gone
* @param {function} [options.setWaitTime=function(waitTime, passes,result,proposed) { ... return exptime.. }]
* @return {Promise} when its all over
*/
ns.expBackoff = function(action, doRetry, options) {
options = options || {};
// this is the default waittime
function defaultWaitTime (waitTime, passes, result) {
return Math.pow(2, passes) * waitTime + Math.round(Math.random() * 73);
}
// default calculation can be bypassed with a custom function
var setWaitTime = function(waitTime, passes, result ,proposed) {
return options.setWaitTime ? options.setWaitTime (waitTime, passes, result,proposed) : 0;
};
// the defaults
var waitTime = options.waitTime || 500;
var passes = options.passes || 0;
var maxPasses = options.maxPasses || 6;
// keep most recent result here
var lastResult;
// the result will actually be a promise
// resolves, or rejects if there's an uncaught failure or we run out of attempts
return new Promise(function(resolve, reject) {
// start
worker(waitTime);
// recursive
function worker(expTime) {
// give up
if (passes >= maxPasses) {
// reject with last known result
reject(lastResult);
}
// we still have some remaining attempts
else {
// call the action with the previous result as argument
// turning it into a promise.resolve will handle both functions and promises
ns.promify(action)
.then(function(result) {
// store what happened for later
lastResult = result;
// pass the result to the retry checker and wait a bit and go again if required
if (doRetry(lastResult, passes++)) {
return ns.handyTimer(expTime)
.then(function() {
var proposedWaitTime = defaultWaitTime(waitTime , passes , result );
worker(setWaitTime(waitTime, passes, lastResult,proposedWaitTime) || proposedWaitTime);
});
}
else {
// finally
resolve(lastResult);
}
});
}
}
});
};