Comparing version 0.0.1 to 0.0.2
@@ -14,16 +14,33 @@ /* | ||
this.exponentialFactor = options.exponentialFactor || 2; | ||
if (options.initialTimeout != undefined && options.initialTimeout < 1) { | ||
throw new Error('The initial timeout must be greater than 0.'); | ||
} else if (options.maxTimeout != undefined && options.maxTimeout < 1) { | ||
throw new Error('The maximal timeout must be greater than 0.'); | ||
} | ||
this.initialTimeout = options.initialTimeout || 100; | ||
this.maxTimeout = options.maxTimeout || 10000; | ||
if (this.maxTimeout <= this.initialTimeout) { | ||
throw new Error('The maximal timeout must be greater ' + | ||
'than the the initial timeout.'); | ||
} | ||
this.backoffInProgress = false; | ||
this.backoffNumber = 0; | ||
this.backoffDelay = 0; | ||
this.timeoutID = -1; | ||
this.handlers = { | ||
backoff: this.onBackoff.bind(this) | ||
}; | ||
}; | ||
util.inherits(ExponentialBackoff, events.EventEmitter); | ||
ExponentialBackoff.prototype.calculateBackoffDelay = function() { | ||
var multiplicativeFactor = Math.pow(this.exponentialFactor, this.backoffNumber); | ||
var delay = Math.min(this.initialTimeout * multiplicativeFactor, this.maxTimeout); | ||
return Math.round(delay); | ||
ExponentialBackoff.prototype.updateBackoffDelay = function() { | ||
if (this.backoffDelay < this.maxTimeout) { | ||
var multiplicativeFactor = Math.pow(2, this.backoffNumber); | ||
var delay = Math.min(this.initialTimeout * multiplicativeFactor, this.maxTimeout); | ||
this.backoffDelay = Math.round(delay); | ||
} | ||
}; | ||
@@ -33,11 +50,8 @@ | ||
if (this.backoffInProgress) { | ||
return false; | ||
} else { | ||
var delay = this.calculateBackoffDelay(); | ||
var onBackoff = this.onBackoff.bind(this, delay); | ||
this.timeoutID = setTimeout(onBackoff, delay); | ||
this.backoffInProgress = true; | ||
this.backoffNumber++; | ||
return true; | ||
throw new Error('Backoff in progress.'); | ||
} | ||
this.updateBackoffDelay(); | ||
this.timeoutID = setTimeout(this.handlers.backoff, this.backoffDelay); | ||
this.backoffInProgress = true; | ||
this.backoffNumber++; | ||
}; | ||
@@ -47,3 +61,3 @@ | ||
this.backoffInProgress = false; | ||
this.emit('backoff', this.backoffNumber, delay); | ||
this.emit('backoff', this.backoffNumber, this.backoffDelay); | ||
}; | ||
@@ -55,2 +69,3 @@ | ||
this.backoffNumber = 0; | ||
this.backoffDelay = 0; | ||
this.emit('reset'); | ||
@@ -57,0 +72,0 @@ }; |
{ | ||
"name": "backoff", | ||
"description": "Exponential backoff implementation.", | ||
"version": "0.0.1", | ||
"version": "0.0.2", | ||
"author": "Mathieu Turcotte <turcotte.mat@gmail.com>", | ||
@@ -6,0 +6,0 @@ "keywords": ["backoff", "exponential"], |
@@ -12,14 +12,24 @@ # Exponential backoff implementation for Node.js | ||
The Backoff object inherits from EventEmitter. One can listen for | ||
backoff completion by listening for 'backoff' events. Registered handlers | ||
will be called with the current backoff number and delay. | ||
In order to use backoff, require `backoff`. | ||
``` js | ||
```js | ||
var Backoff = require('backoff'); | ||
``` | ||
var backoff = new Backoff(); | ||
`Backoff` inherits from `EventEmitter`. One can listen for backoff completion | ||
by listening for `backoff` events. Registered handlers will be called with the | ||
current backoff number and delay. | ||
``` js | ||
var backoff = new Backoff({ | ||
initialTimeout: 10, | ||
maxTimeout: 1000 | ||
}); | ||
backoff.on('backoff', function(number, delay) { | ||
// Retry operation... | ||
backoff.backoff(); | ||
console.log(number + ' ' + delay + 'ms'); | ||
if (number < 10) { | ||
backoff.backoff(); | ||
} | ||
}); | ||
@@ -30,24 +40,59 @@ | ||
It's also possible to reset 'Backoff' instance. Once reset, a 'Backoff' | ||
instance can be reused. On reset, the 'reset' event will be emitted. | ||
The previous example would print: | ||
```js | ||
var Backoff = require('backoff'); | ||
``` | ||
1 10ms | ||
2 20ms | ||
3 40ms | ||
4 80ms | ||
5 160ms | ||
6 320ms | ||
7 640ms | ||
8 1000ms | ||
9 1000ms | ||
10 1000ms | ||
``` | ||
var backoff = new Backoff(); | ||
## API | ||
backoff.on('backoff', function(number, delay) { | ||
backoff.backoff(); | ||
}); | ||
### new Backoff([options]) | ||
backoff.on('reset', function() { | ||
console.log('reset'); | ||
}); | ||
Construct a new backoff object. | ||
backoff.backoff(); | ||
`options` is an object with the following defaults: | ||
setTimeout(function() { | ||
backoff.reset(); | ||
}, 5000); | ||
```js | ||
options = { | ||
initialTimeout: 100, | ||
maxTimeout: 10000 | ||
}; | ||
``` | ||
With these values, the timeout delay will exponentially increase from 100ms to | ||
1000ms. | ||
### backoff.backoff() | ||
Start a backoff operation, doubling the previous timeout. | ||
Returns true on success and false if a backoff was already in progress. | ||
### backoff.reset() | ||
Reset the backoff object state. If a backoff operation is in progress when | ||
called, it will be stop. After reset, a backoff instance can be reused. | ||
### Event: 'backoff' | ||
- number: number of backoff since last reset | ||
- delay: current backoff delay | ||
Emitted on backoff completion. | ||
### Event: 'reset' | ||
Emitted when a backoff instance is reset. | ||
## License | ||
This code is free to use under the terms of the [MIT license](http://mturcotte.mit-license.org/). |
@@ -25,6 +25,5 @@ /* | ||
"the 'backoff' event should be emitted on backoff completion": function(test) { | ||
"'backoff' event should be emitted on backoff completion": function(test) { | ||
var backoff = new Backoff({ | ||
initialTimeout: 10, | ||
maxTimeout: 1000 | ||
}); | ||
@@ -34,14 +33,10 @@ var spy = new sinon.spy(); | ||
var expectedDelays = [10, 20, 40, 80, 160, 320, 640, 1000]; | ||
for (var i = 0; i < expectedDelays.length; i++) { | ||
backoff.backoff(); | ||
this.clock.tick(expectedDelays[i]); | ||
test.ok(spy.calledWith(i + 1, expectedDelays[i])); | ||
spy.reset(); | ||
} | ||
backoff.backoff(); | ||
this.clock.tick(10); | ||
test.ok(spy.calledOnce, 'backoff event has not been emitted'); | ||
test.done(); | ||
}, | ||
"the 'reset' event should be emitted on reset": function(test) { | ||
"'reset' event should be emitted on reset": function(test) { | ||
var backoff = new Backoff(); | ||
@@ -53,13 +48,114 @@ var reset = sinon.spy(); | ||
test.ok(reset.calledOnce); | ||
test.ok(reset.calledOnce, 'reset event has not been emitted'); | ||
test.done(); | ||
}, | ||
"call to backoff should be ignored when a backoff is in progress": function(test) { | ||
"the backoff delay should increase exponentially from initialTimeout to maxTimeout": function(test) { | ||
var backoff = new Backoff({ | ||
initialTimeout: 10, | ||
maxTimeout: 1000 | ||
}); | ||
var spy = new sinon.spy(); | ||
backoff.on('backoff', spy); | ||
var clock = this.clock; | ||
var delays = [10, 20, 40, 80, 160, 320, 640, 1000, 1000]; | ||
delays.forEach(function(delay, i) { | ||
backoff.backoff(); | ||
clock.tick(delay); | ||
}); | ||
delays.forEach(function(delay, i) { | ||
test.equals(spy.getCall(i).args[0], i + 1); | ||
test.equals(spy.getCall(i).args[1], delay); | ||
}); | ||
test.done(); | ||
}, | ||
"the initial timeout should be greater than 0": function(test) { | ||
test.throws(function() { | ||
var backoff = new Backoff({ | ||
initialTimeout: -1 | ||
}); | ||
}); | ||
test.throws(function() { | ||
var backoff = new Backoff({ | ||
initialTimeout: 0 | ||
}); | ||
}); | ||
test.doesNotThrow(function() { | ||
var backoff = new Backoff({ | ||
initialTimeout: 1 | ||
}); | ||
}); | ||
test.done(); | ||
}, | ||
"the maximal timeout should be greater than 0": function(test) { | ||
test.throws(function() { | ||
var backoff = new Backoff({ | ||
maxTimeout: -1 | ||
}); | ||
}); | ||
test.throws(function() { | ||
var backoff = new Backoff({ | ||
maxTimeout: 0 | ||
}); | ||
}); | ||
test.done(); | ||
}, | ||
"the maximal timeout should be greater than the original timeout": function(test) { | ||
test.throws(function() { | ||
var backoff = new Backoff({ | ||
initialTimeout: 10, | ||
maxTimeout: 10 | ||
}); | ||
}); | ||
test.doesNotThrow(function() { | ||
var backoff = new Backoff({ | ||
initialTimeout: 10, | ||
maxTimeout: 11 | ||
}); | ||
}); | ||
test.done(); | ||
}, | ||
"call to backoff while a backoff is in progress should throw an error": function(test) { | ||
var backoff = new Backoff(); | ||
test.ok(backoff.backoff() == true); | ||
test.ok(backoff.backoff() == false); | ||
backoff.backoff(); | ||
test.throws(function() { | ||
backoff.backoff(); | ||
}); | ||
test.done(); | ||
}, | ||
"calling reset when a backoff is in progress should disarm the timeout": function(test) { | ||
var backoff = new Backoff({ | ||
initialTimeout: 10 | ||
}); | ||
var spy = new sinon.spy(); | ||
backoff.on('backoff', spy); | ||
backoff.backoff(); | ||
backoff.reset(); | ||
this.clock.tick(100); // 'backoff' should not be emitted. | ||
test.equals(spy.callCount, 0, "backoff timeout did trigger"); | ||
test.done(); | ||
}, | ||
"it should be possible to reuse a backoff instance after reset": function(test) { | ||
@@ -66,0 +162,0 @@ var backoff = new Backoff({ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
10964
229
97