Skip to content

Commit

Permalink
1.2.0 highWater mark strategies
Browse files Browse the repository at this point in the history
  • Loading branch information
SGrondin committed Jun 7, 2014
1 parent 875ceeb commit bff9c54
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 41 deletions.
49 changes: 35 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ __Browser__
<script type="text/javascript" src="bottleneck.min.js"></script>
```

#Usage
#Example

Most APIs have a rate limit. For example, the Reddit.com API limits programs to 1 request every 2 seconds.

Expand All @@ -29,32 +29,53 @@ var Bottleneck = require("bottleneck"); //Node.JS only
var limiter = new Bottleneck(1, 2000);
```

```new Bottleneck(maxNb, minTime);```

* maxNb : How many requests can be running at the same time. 0 for unlimited.
* minTime : Optional. How long to wait after launching a request before launching another one.


Instead of doing
```javascript
someAsyncCall(arg1, arg2, argN, callback);
```
You do
You now do
```javascript
limiter.submit(someAsyncCall, arg1, arg2, argN, callback);
```
And now you can be assured that someAsyncCall will follow the rate guidelines!
And now you can be assured that someAsyncCall will abide by your rate guidelines!

All the submitted requests will be executed *in order*.

#Docs

###Constructor
```new Bottleneck(maxNb, minTime, highWater, strategy);```

* maxNb : How many requests can be running at the same time. Default: 0 (unlimited)
* minTime : How long to wait after launching a request before launching another one. Default: 0ms
* highWater : How long can the queue get? Default: 0 (unlimited)
* strategy : Which strategy use if the queue gets longer than the high water mark. Default: Bottleneck.strategy.LEAK.

###submit()

This adds a request to the queue.

It returns true if the queue's length is under the high water mark, otherwise it returns false.

If a callback isn't necessary, you must pass ```null``` instead.

###strategies

####Bottleneck.strategy.LEAK
When submitting a new request, if the highWater mark is reached, drop the oldest request in the queue. This is useful when requests that have been waiting for too long are not important anymore.

####Bottleneck.strategy.OVERFLOW
When submitting a new request, if the highWater mark is reached, do not add that request. The ```submit``` call did nothing.

If a callback isn't necessary, pass ```null``` instead.

###stopAll
###stopAll()
```javascript
limiter.stopAll();
```
Cancels all queued up requests and prevents additonal requests from being submitted.

###changeSettings
###changeSettings()
```javascript
limiter.changeSettings(maxNb, minTime)
limiter.changeSettings(maxNb, minTime, highWater, strategy)
```
Same parameters as the constructor, pass ```null``` to skip a parameter.
Same parameters as the constructor, pass ```null``` to skip a parameter and keep it to its current value.
33 changes: 26 additions & 7 deletions bottleneck.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,16 @@
__slice = [].slice;

Bottleneck = (function() {
function Bottleneck(maxNb, minTime) {
Bottleneck.strategy = Bottleneck.prototype.strategy = {
LEAK: 1,
OVERFLOW: 2
};

function Bottleneck(maxNb, minTime, highWater, strategy) {
this.maxNb = maxNb != null ? maxNb : 0;
this.minTime = minTime != null ? minTime : 0;
this.highWater = highWater != null ? highWater : 0;
this.strategy = strategy != null ? strategy : Bottleneck.prototype.strategy.LEAK;
this._nextRequest = Date.now();
this._nbRunning = 0;
this._queue = [];
Expand All @@ -22,13 +29,13 @@
this._nextRequest = Date.now() + wait + this.minTime;
next = this._queue.shift();
done = false;
return index = this._timeouts.push(setTimeout((function(_this) {
return index = -1 + this._timeouts.push(setTimeout((function(_this) {
return function() {
return next.task.apply({}, next.args.concat(function() {
var _ref;
if (!done) {
done = true;
delete _this._timeouts[index - 1];
delete _this._timeouts[index];
_this._nbRunning--;
_this._tryToRun();
return (_ref = next.cb) != null ? _ref.apply({}, Array.prototype.slice.call(arguments, 0)) : void 0;
Expand All @@ -40,19 +47,30 @@
};

Bottleneck.prototype.submit = function() {
var args, cb, task, _i;
var args, cb, reachedHighWaterMark, task, _i;
task = arguments[0], args = 3 <= arguments.length ? __slice.call(arguments, 1, _i = arguments.length - 1) : (_i = 1, []), cb = arguments[_i++];
reachedHighWaterMark = this.highWater > 0 && this._queue.length === this.highWater;
if (reachedHighWaterMark) {
if (this.strategy === Bottleneck.prototype.strategy.LEAK) {
this._queue.shift();
} else if (this.strategy === Bottleneck.prototype.strategy.OVERFLOW) {
return reachedHighWaterMark;
}
}
this._queue.push({
task: task,
args: args,
cb: cb
});
return this._tryToRun();
this._tryToRun();
return reachedHighWaterMark;
};

Bottleneck.prototype.changeSettings = function(maxNb, minTime) {
Bottleneck.prototype.changeSettings = function(maxNb, minTime, highWater, strategy) {
this.maxNb = maxNb != null ? maxNb : this.maxNb;
this.minTime = minTime != null ? minTime : this.minTime;
this.highWater = highWater != null ? highWater : this.highWater;
this.strategy = strategy != null ? strategy : this.strategy;
return this;
};

Expand All @@ -63,7 +81,8 @@
a = _ref[_i];
clearTimeout(a);
}
return this._tryToRun = function() {};
this._tryToRun = function() {};
return this.submit = function() {};
};

return Bottleneck;
Expand Down
2 changes: 1 addition & 1 deletion bottleneck.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 26 additions & 7 deletions lib/Bottleneck.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
{
"name": "bottleneck",
"version": "1.1.2",
"version": "1.2.0",
"description": "Async rate limiter",
"main": "lib/index.js",
"scripts": {
"test": "echo \"No test specified\" && exit 1"
"test": "echo \"No test specified\" && exit 1",
"make": "./scripts/recompile.sh"
},
"repository": {
"type": "git",
Expand All @@ -17,7 +18,8 @@
"queues",
"timing",
"limiter",
"load"
"load",
"synchronize"
],
"author": {
"name": "Simon Grondin"
Expand Down
4 changes: 0 additions & 4 deletions recompile.sh → scripts/recompile.sh
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
#!/usr/bin/env bash

DIR=$(dirname $0)
pushd $DIR > /dev/null

if [[ ! -d node_modules ]]; then
echo 'Installing compiler tools...'
sleep 1
Expand All @@ -17,5 +14,4 @@ mv src/*.js lib/
node_modules/browserify/bin/cmd.js lib/index.js > bottleneck.js
node_modules/uglify-js/bin/uglifyjs bottleneck.js -o bottleneck.min.js

popd > /dev/null
echo 'Done!'
17 changes: 12 additions & 5 deletions src/Bottleneck.coffee
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
class Bottleneck
constructor: (@maxNb=0, @minTime=0) ->
Bottleneck.strategy = Bottleneck::strategy = {LEAK:1, OVERFLOW:2}
constructor: (@maxNb=0, @minTime=0, @highWater=0, @strategy=Bottleneck::strategy.LEAK) ->
@_nextRequest = Date.now()
@_nbRunning = 0
@_queue = []
Expand All @@ -11,21 +12,27 @@ class Bottleneck
@_nextRequest = Date.now() + wait + @minTime
next = @_queue.shift()
done = false
index = @_timeouts.push setTimeout () =>
index = -1 + @_timeouts.push setTimeout () =>
next.task.apply {}, next.args.concat () =>
if not done
done = true
delete @_timeouts[index-1]
delete @_timeouts[index]
@_nbRunning--
@_tryToRun()
next.cb?.apply {}, Array::slice.call arguments, 0
, wait
submit: (task, args..., cb) ->
reachedHighWaterMark = @highWater > 0 and @_queue.length == @highWater
if reachedHighWaterMark
if @strategy == Bottleneck::strategy.LEAK then @_queue.shift()
else if @strategy == Bottleneck::strategy.OVERFLOW then return reachedHighWaterMark
@_queue.push {task, args, cb}
@_tryToRun()
changeSettings: (@maxNb=@maxNb, @minTime=@minTime) -> @
reachedHighWaterMark
changeSettings: (@maxNb=@maxNb, @minTime=@minTime, @highWater=@highWater, @strategy=@strategy) -> @
stopAll: ->
(clearTimeout a for a in @_timeouts)
@_tryToRun = -> # Ugly, but it's that or more global state
@_tryToRun = ->
@submit = ->

module.exports = Bottleneck

0 comments on commit bff9c54

Please sign in to comment.