1function RetryOperation(timeouts, options) { 2 // Compatibility for the old (timeouts, retryForever) signature 3 if (typeof options === 'boolean') { 4 options = { forever: options }; 5 } 6 7 this._originalTimeouts = JSON.parse(JSON.stringify(timeouts)); 8 this._timeouts = timeouts; 9 this._options = options || {}; 10 this._maxRetryTime = options && options.maxRetryTime || Infinity; 11 this._fn = null; 12 this._errors = []; 13 this._attempts = 1; 14 this._operationTimeout = null; 15 this._operationTimeoutCb = null; 16 this._timeout = null; 17 this._operationStart = null; 18 19 if (this._options.forever) { 20 this._cachedTimeouts = this._timeouts.slice(0); 21 } 22} 23module.exports = RetryOperation; 24 25RetryOperation.prototype.reset = function() { 26 this._attempts = 1; 27 this._timeouts = this._originalTimeouts; 28} 29 30RetryOperation.prototype.stop = function() { 31 if (this._timeout) { 32 clearTimeout(this._timeout); 33 } 34 35 this._timeouts = []; 36 this._cachedTimeouts = null; 37}; 38 39RetryOperation.prototype.retry = function(err) { 40 if (this._timeout) { 41 clearTimeout(this._timeout); 42 } 43 44 if (!err) { 45 return false; 46 } 47 var currentTime = new Date().getTime(); 48 if (err && currentTime - this._operationStart >= this._maxRetryTime) { 49 this._errors.unshift(new Error('RetryOperation timeout occurred')); 50 return false; 51 } 52 53 this._errors.push(err); 54 55 var timeout = this._timeouts.shift(); 56 if (timeout === undefined) { 57 if (this._cachedTimeouts) { 58 // retry forever, only keep last error 59 this._errors.splice(this._errors.length - 1, this._errors.length); 60 this._timeouts = this._cachedTimeouts.slice(0); 61 timeout = this._timeouts.shift(); 62 } else { 63 return false; 64 } 65 } 66 67 var self = this; 68 var timer = setTimeout(function() { 69 self._attempts++; 70 71 if (self._operationTimeoutCb) { 72 self._timeout = setTimeout(function() { 73 self._operationTimeoutCb(self._attempts); 74 }, self._operationTimeout); 75 76 if (self._options.unref) { 77 self._timeout.unref(); 78 } 79 } 80 81 self._fn(self._attempts); 82 }, timeout); 83 84 if (this._options.unref) { 85 timer.unref(); 86 } 87 88 return true; 89}; 90 91RetryOperation.prototype.attempt = function(fn, timeoutOps) { 92 this._fn = fn; 93 94 if (timeoutOps) { 95 if (timeoutOps.timeout) { 96 this._operationTimeout = timeoutOps.timeout; 97 } 98 if (timeoutOps.cb) { 99 this._operationTimeoutCb = timeoutOps.cb; 100 } 101 } 102 103 var self = this; 104 if (this._operationTimeoutCb) { 105 this._timeout = setTimeout(function() { 106 self._operationTimeoutCb(); 107 }, self._operationTimeout); 108 } 109 110 this._operationStart = new Date().getTime(); 111 112 this._fn(this._attempts); 113}; 114 115RetryOperation.prototype.try = function(fn) { 116 console.log('Using RetryOperation.try() is deprecated'); 117 this.attempt(fn); 118}; 119 120RetryOperation.prototype.start = function(fn) { 121 console.log('Using RetryOperation.start() is deprecated'); 122 this.attempt(fn); 123}; 124 125RetryOperation.prototype.start = RetryOperation.prototype.try; 126 127RetryOperation.prototype.errors = function() { 128 return this._errors; 129}; 130 131RetryOperation.prototype.attempts = function() { 132 return this._attempts; 133}; 134 135RetryOperation.prototype.mainError = function() { 136 if (this._errors.length === 0) { 137 return null; 138 } 139 140 var counts = {}; 141 var mainError = null; 142 var mainErrorCount = 0; 143 144 for (var i = 0; i < this._errors.length; i++) { 145 var error = this._errors[i]; 146 var message = error.message; 147 var count = (counts[message] || 0) + 1; 148 149 counts[message] = count; 150 151 if (count >= mainErrorCount) { 152 mainError = error; 153 mainErrorCount = count; 154 } 155 } 156 157 return mainError; 158}; 159