123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117 |
- 'use strict';
- var Promise = require('any-promise');
- var util = require('util');
- var format = util.format;
- function TimeoutError(message, err) {
- Error.call(this);
- Error.captureStackTrace(this, TimeoutError);
- this.name = 'TimeoutError';
- this.message = message;
- this.previous = err;
- }
- util.inherits(TimeoutError, Error);
- 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);
- // 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 = Math.pow(
- options.backoffBase,
- Math.pow(options.backoffExponent, options.$current - 1)
- );
- // Do some accounting
- options.$current++;
- options.report(format('Retrying %s (%s)', options.name, options.$current), options);
- if (retryDelay) {
- // Use backoff function to ease retry rate
- options.report(format('Delaying retry of %s by %s', options.name, 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;
|