Skip to content

Commit

Permalink
1.14.0 'idle' event, nbRunning(), removeAllListeners()
Browse files Browse the repository at this point in the history
  • Loading branch information
simon committed Oct 5, 2016
1 parent f2242ed commit e85979d
Show file tree
Hide file tree
Showing 8 changed files with 169 additions and 49 deletions.
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,13 @@ limiter.nbQueued(priority);

`priority` is optional. Without that argument, it'll return the total number of requests waiting to be executed, otherwise it'll only count the number of requests with that specific priority.

### nbRunning()

```js
limiter.nbRunning();
```

Returns the number of requests currently running in the limiter.

### check()

Expand Down Expand Up @@ -202,21 +209,29 @@ Cancels all *queued up* requests and prevents additonal requests from being adde

### Events

Event names: `empty`, `dropped`.
Event names: `empty`, `idle`, `dropped`.

```js
limiter.on('empty', function () {
// This will be called when the nbQueued() drops to 0.
})
```

```js
limiter.on('idle', function () {
// This will be called when the nbQueued() drops to 0 AND there is nothing currently running in the limiter.
})
```

```js
limiter.on('dropped', function (dropped) {
// This will be called when a strategy was triggered.
// The dropped request is passed to this callback.
})
```

Use `removeAllListeners()` with an optional event name as first argument to remove listeners.

**Note:** It's possible to set multiple callbacks to the same event name.


Expand Down
27 changes: 25 additions & 2 deletions bottleneck.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,18 @@
}
};

Bottleneck.prototype.nbRunning = function() {
return this._nbRunning;
};

Bottleneck.prototype._getFirst = function(arr) {
return this._find(arr, function(x) {
return x.length > 0;
});
};

Bottleneck.prototype._conditionsCheck = function() {
return (this._nbRunning < this.maxNb || this.maxNb <= 0) && ((this.reservoir == null) || this.reservoir > 0);
return (this.nbRunning() < this.maxNb || this.maxNb <= 0) && ((this.reservoir == null) || this.reservoir > 0);
};

Bottleneck.prototype.check = function() {
Expand Down Expand Up @@ -159,6 +163,9 @@
delete _this._running[index];
_this._nbRunning--;
_this._tryToRun();
if (_this.nbRunning() === 0 && _this.nbQueued() === 0) {
_this._trigger("idle", []);
}
if (!_this.interrupt) {
return (ref = next.cb) != null ? ref.apply({}, Array.prototype.slice.call(arguments, 0)) : void 0;
}
Expand Down Expand Up @@ -284,6 +291,18 @@
return this;
};

Bottleneck.prototype.removeAllListeners = function(name) {
if (name == null) {
name = null;
}
if (name != null) {
delete this.events[name];
} else {
this.events = {};
}
return this;
};

Bottleneck.prototype.stopAll = function(interrupt) {
var a, j, job, k, len, len1, ref, ref1;
this.interrupt = interrupt != null ? interrupt : this.interrupt;
Expand All @@ -306,7 +325,11 @@
while (job = (this._getFirst(this._queues)).shift()) {
this._trigger("dropped", [job]);
}
return this._trigger("empty", []);
this._trigger("empty", []);
if (this.nbRunning() === 0) {
this._trigger("idle", []);
}
return this;
};

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

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion bower.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "bottleneck",
"main": "bottleneck.js",
"version": "1.13.1",
"version": "1.14.0",
"homepage": "https://github.com/SGrondin/bottleneck",
"authors": [
"SGrondin <github@simongrondin.name>"
Expand Down
27 changes: 25 additions & 2 deletions lib/Bottleneck.js

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "bottleneck",
"version": "1.13.1",
"version": "1.14.0",
"description": "Async rate limiter",
"main": "lib/index.js",
"scripts": {
Expand Down
9 changes: 8 additions & 1 deletion src/Bottleneck.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ class Bottleneck
if sProperty < 0 then 0 else if sProperty > NB_PRIORITIES-1 then NB_PRIORITIES-1 else sProperty
_find: (arr, fn) -> (for x, i in arr then if fn x then return x); []
nbQueued: (priority) -> if priority? then @_queues[@_sanitizePriority priority].length else @_queues.reduce ((a, b) -> a+b.length), 0
nbRunning: () -> @_nbRunning
_getFirst: (arr) -> @_find arr, (x) -> x.length > 0
_conditionsCheck: -> (@_nbRunning < @maxNb or @maxNb <= 0) and (not @reservoir? or @reservoir > 0)
_conditionsCheck: -> (@nbRunning() < @maxNb or @maxNb <= 0) and (not @reservoir? or @reservoir > 0)
check: -> @_conditionsCheck() and (@_nextRequest-Date.now()) <= 0
_tryToRun: ->
if @_conditionsCheck() and (queued = @nbQueued()) > 0
Expand All @@ -48,6 +49,7 @@ class Bottleneck
delete @_running[index]
@_nbRunning--
@_tryToRun()
if @nbRunning() == 0 and @nbQueued() == 0 then @_trigger "idle", []
if not @interrupt then next.cb?.apply {}, Array::slice.call arguments, 0
if @limiter? then @limiter.submit.apply @limiter, Array::concat next.task, next.args, completed
else next.task.apply {}, next.args.concat completed
Expand Down Expand Up @@ -97,12 +99,17 @@ class Bottleneck
on: (name, cb) ->
if @events[name]? then @events[name].push cb else @events[name] = [cb]
@
removeAllListeners: (name=null) ->
if name? then delete @events[name] else @events = {}
@
stopAll: (@interrupt=@interrupt) ->
(clearTimeout a.timeout for a in @_running)
@_tryToRun = ->
@check = @submit = @submitPriority = @schedule = @schedulePriority = -> false
if @interrupt then (@_trigger "dropped", [a.job] for a in @_running)
while job = (@_getFirst @_queues).shift() then @_trigger "dropped", [job]
@_trigger "empty", []
if @nbRunning() == 0 then @_trigger "idle", []
@

module.exports = Bottleneck
132 changes: 92 additions & 40 deletions test/general.js
Original file line number Diff line number Diff line change
@@ -1,69 +1,95 @@
describe('General', function () {
describe('nbQueued', function () {

it('Should return the nbQueued with and without a priority value', function (done) {
var c = makeTest(1, 250)
it('Should return the nbQueued with and without a priority value', function (done) {
var c = makeTest(1, 250)

console.assert(c.limiter.nbQueued() === 0)
console.assert(c.limiter.nbQueued() === 0)

c.limiter.submit(c.job, null, 1, c.noErrVal(1))
console.assert(c.limiter.nbQueued() === 0) // It's already running
c.limiter.submit(c.job, null, 1, c.noErrVal(1))
console.assert(c.limiter.nbQueued() === 0) // It's already running

c.limiter.submit(c.job, null, 2, c.noErrVal(2))
console.assert(c.limiter.nbQueued() === 1)
console.assert(c.limiter.nbQueued(1) === 0)
console.assert(c.limiter.nbQueued(5) === 1)
c.limiter.submit(c.job, null, 2, c.noErrVal(2))
console.assert(c.limiter.nbQueued() === 1)
console.assert(c.limiter.nbQueued(1) === 0)
console.assert(c.limiter.nbQueued(5) === 1)

c.limiter.submit(c.job, null, 3, c.noErrVal(3))
console.assert(c.limiter.nbQueued() === 2)
console.assert(c.limiter.nbQueued(1) === 0)
console.assert(c.limiter.nbQueued(5) === 2)
c.limiter.submit(c.job, null, 3, c.noErrVal(3))
console.assert(c.limiter.nbQueued() === 2)
console.assert(c.limiter.nbQueued(1) === 0)
console.assert(c.limiter.nbQueued(5) === 2)

c.limiter.submit(c.job, null, 4, c.noErrVal(4))
console.assert(c.limiter.nbQueued() === 3)
console.assert(c.limiter.nbQueued(1) === 0)
console.assert(c.limiter.nbQueued(5) === 3)
c.limiter.submit(c.job, null, 4, c.noErrVal(4))
console.assert(c.limiter.nbQueued() === 3)
console.assert(c.limiter.nbQueued(1) === 0)
console.assert(c.limiter.nbQueued(5) === 3)

c.limiter.submitPriority(1, c.job, null, 5, c.noErrVal(5))
console.assert(c.limiter.nbQueued() === 4)
console.assert(c.limiter.nbQueued(1) === 1)
console.assert(c.limiter.nbQueued(5) === 3)
c.limiter.submitPriority(1, c.job, null, 5, c.noErrVal(5))
console.assert(c.limiter.nbQueued() === 4)
console.assert(c.limiter.nbQueued(1) === 1)
console.assert(c.limiter.nbQueued(5) === 3)

c.last(function (err, results) {
console.assert(c.limiter.nbQueued() === 0)
c.checkResultsOrder([1,5,2,3,4])
c.checkDuration(1000)
console.assert(c.asserts() === 10)
done()
})
c.last(function (err, results) {
console.assert(c.limiter.nbQueued() === 0)
c.checkResultsOrder([1,5,2,3,4])
c.checkDuration(1000)
console.assert(c.asserts() === 10)
done()
})
})

it('Should return the nbRunning', function (done) {
var c = makeTest(2, 250)

console.assert(c.limiter.nbRunning() === 0)

c.limiter.submit(c.job, null, 1, c.noErrVal(1))
console.assert(c.limiter.nbRunning() === 1)

setTimeout(function () {
console.assert(c.limiter.nbRunning() === 0)
setTimeout(function () {
c.limiter.submit(c.job, null, 1, c.noErrVal(1))
c.limiter.submit(c.job, null, 2, c.noErrVal(2))
c.limiter.submit(c.job, null, 3, c.noErrVal(3))
c.limiter.submit(c.job, null, 4, c.noErrVal(4))
console.assert(c.limiter.nbRunning() === 2)
done()
}, 0)
}, 0)
})

describe('Events', function () {
it('Should fire callback on empty queue', function (done) {
it('Should fire events on empty queue', function (done) {
var c = makeTest(1, 250)
var called = 0
var calledEmpty = 0
var calledIdle = 0

c.limiter.on('empty', function () { called++ })
c.limiter.on('empty', function () { calledEmpty++ })
c.limiter.on('idle', function () { calledIdle++ })

c.pNoErrVal(c.limiter.schedule(c.promise, null, 1), 1)
c.pNoErrVal(c.limiter.schedule(c.promise, null, 2), 2)
c.pNoErrVal(c.limiter.schedule(c.promise, null, 3), 3)
c.last(function (err, results) {
c.checkResultsOrder([1,2,3])
c.checkDuration(500)
console.assert(c.asserts() === 3)
console.assert(called === 2)
done()
c.limiter.on('idle', function () {
c.limiter.removeAllListeners()
c.last(function (err, results) {
c.checkResultsOrder([1,2,3])
c.checkDuration(500)
console.assert(calledEmpty === 2)
console.assert(calledIdle === 1)
done()
})
})
})

it('Should fire events when calling stopAll()', function (done) {
it('Should fire events when calling stopAll() (sync)', function (done) {
var c = makeTest(1, 250)
var calledEmpty = 0
var calledIdle = 0
var calledDropped = 0

c.limiter.on('empty', function () { calledEmpty++ })
c.limiter.on('idle', function () { calledIdle++ })
c.limiter.on('dropped', function () { calledDropped++ })

c.pNoErrVal(c.limiter.schedule(c.promise, null, 1), 1)
Expand All @@ -74,8 +100,34 @@ describe('General', function () {
setTimeout(function () {
console.assert(calledEmpty === 2)
console.assert(calledDropped === 2)
console.assert(calledIdle === 0)
done()
}, 30)
})

it('Should fire events when calling stopAll() (async)', function (done) {
var c = makeTest(1, 250)
var calledEmpty = 0
var calledIdle = 0
var calledDropped = 0

c.limiter.on('empty', function () { calledEmpty++ })
c.limiter.on('idle', function () { calledIdle++ })
c.limiter.on('dropped', function () { calledDropped++ })

c.pNoErrVal(c.limiter.schedule(c.promise, null, 1), 1)
c.pNoErrVal(c.limiter.schedule(c.promise, null, 2), 2)
c.pNoErrVal(c.limiter.schedule(c.promise, null, 3), 3)

setTimeout(function () {
c.limiter.stopAll()
}, 0)
setTimeout(function () {
console.assert(calledEmpty === 2)
console.assert(calledDropped === 2)
console.assert(calledIdle === 1)
done()
}, 20)
}, 30)
})

it('Should fail when rejectOnDrop is true', function (done) {
Expand Down

0 comments on commit e85979d

Please sign in to comment.