You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
108 lines
3.3 KiB
108 lines
3.3 KiB
'use strict'; |
|
|
|
class TimeoutError extends Error { |
|
constructor(message, err) { |
|
super(message); |
|
this.name = "TimeoutError"; |
|
this.previous = err; |
|
} |
|
} |
|
|
|
function matches(match, err) { |
|
if (match === true) return true; |
|
if (typeof match === 'function') { |
|
try { |
|
if (err instanceof match) return true; |
|
} catch (_) { |
|
return !!match(err); |
|
} |
|
} |
|
if (match === err.toString()) return true; |
|
if (match === err.message) return true; |
|
return match instanceof RegExp |
|
&& (match.test(err.message) || match.test(err.toString())); |
|
} |
|
|
|
module.exports = function retryAsPromised(callback, options) { |
|
if (!callback || !options) { |
|
throw new Error( |
|
'retry-as-promised must be passed a callback and a options set or a number' |
|
); |
|
} |
|
|
|
if (typeof options === 'number') { |
|
options = { |
|
max: options |
|
}; |
|
} |
|
|
|
// Super cheap clone |
|
options = { |
|
$current: options.$current || 1, |
|
max: options.max, |
|
timeout: options.timeout || undefined, |
|
match: options.match || [], |
|
backoffBase: options.backoffBase === undefined ? 100 : options.backoffBase, |
|
backoffExponent: options.backoffExponent || 1.1, |
|
report: options.report || function () {}, |
|
name: options.name || callback.name || 'unknown' |
|
}; |
|
|
|
if (!Array.isArray(options.match)) options.match = [options.match]; |
|
options.report('Trying ' + options.name + ' #' + options.$current + ' at ' + new Date().toLocaleTimeString(), options); |
|
|
|
return new Promise(function(resolve, reject) { |
|
var timeout, backoffTimeout, lastError; |
|
|
|
if (options.timeout) { |
|
timeout = setTimeout(function() { |
|
if (backoffTimeout) clearTimeout(backoffTimeout); |
|
reject(new TimeoutError(options.name + ' timed out', lastError)); |
|
}, options.timeout); |
|
} |
|
|
|
Promise.resolve(callback({ current: options.$current })) |
|
.then(resolve) |
|
.then(function() { |
|
if (timeout) clearTimeout(timeout); |
|
if (backoffTimeout) clearTimeout(backoffTimeout); |
|
}) |
|
.catch(function(err) { |
|
if (timeout) clearTimeout(timeout); |
|
if (backoffTimeout) clearTimeout(backoffTimeout); |
|
|
|
lastError = err; |
|
options.report((err && err.toString()) || err, options, err); |
|
|
|
// Should not retry if max has been reached |
|
var shouldRetry = options.$current < options.max; |
|
if (!shouldRetry) return reject(err); |
|
shouldRetry = options.match.length === 0 || options.match.some(function (match) { |
|
return matches(match, err) |
|
}); |
|
if (!shouldRetry) return reject(err); |
|
|
|
var retryDelay = options.backoffBase * Math.pow(options.backoffExponent, options.$current - 1); |
|
|
|
// Do some accounting |
|
options.$current++; |
|
options.report(`Retrying ${options.name} (${options.$current})`, options); |
|
|
|
if (retryDelay) { |
|
// Use backoff function to ease retry rate |
|
options.report(`Delaying retry of ${options.name} by ${retryDelay}`, options); |
|
backoffTimeout = setTimeout(function() { |
|
retryAsPromised(callback, options) |
|
.then(resolve) |
|
.catch(reject); |
|
}, retryDelay); |
|
} else { |
|
retryAsPromised(callback, options) |
|
.then(resolve) |
|
.catch(reject); |
|
} |
|
}); |
|
}); |
|
}; |
|
|
|
module.exports.TimeoutError = TimeoutError;
|
|
|