index.js
3.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
var debug = require('debug')('retry-as-promised')
, error = require('debug')('retry-as-promised:error')
, Promise = require('bluebird');
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 || null,
name: options.name || callback.name || 'unknown'
};
// Massage match option into array so we can blindly treat it as such later
if (!Array.isArray(options.match)) options.match = [options.match];
if(options.report) options.report('Trying ' + options.name + ' #' + options.$current + ' at ' + new Date().toLocaleTimeString(), options);
return new Promise(function (resolve, reject) {
var timeout, backoffTimeout;
if (options.timeout) {
timeout = setTimeout(function () {
if (backoffTimeout) clearTimeout(backoffTimeout);
reject(Promise.TimeoutError( options.name + ' timed out'));
}, options.timeout);
}
Promise.resolve(callback({ current: options.$current })).then(resolve).tap(function () {
if (timeout) clearTimeout(timeout);
if (backoffTimeout) clearTimeout(backoffTimeout);
}).catch(function (err) {
if (timeout) clearTimeout(timeout);
if (backoffTimeout) clearTimeout(backoffTimeout);
error(err && err.toString() || err);
if (options.report) options.report('Try ' + options.name + ' #' + options.$current + ' failed: ' + err.toString(), options, err);
// Should not retry if max has been reached
var shouldRetry = options.$current < options.max;
if (shouldRetry && options.match.length && err) {
// If match is defined we should fail if it is not met
shouldRetry = options.match.reduce(function (shouldRetry, match) {
if (shouldRetry) return shouldRetry;
if (match === err.toString() ||
match === err.message ||
(typeof match === "function" && err instanceof match) ||
(match instanceof RegExp && (match.test(err.message) || match.test(err.toString()) ))
) {
shouldRetry = true;
}
return shouldRetry;
}, false);
}
if (!shouldRetry) return reject(err);
var retryDelay = Math.pow(options.backoffBase, Math.pow(options.backoffExponent, (options.$current - 1)));
// Do some accounting
options.$current++;
debug('Retrying '+ options.name + ' (%s)', options.$current);
if (retryDelay) {
// Use backoff function to ease retry rate
debug('Delaying retry of ' + options.name + ' by %s', retryDelay);
if(options.report) 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);
}
});
});
};