From bad815ebd01a742e43aef2ae0ff61bfc69d05e7d Mon Sep 17 00:00:00 2001 From: SGrondin Date: Sun, 15 Mar 2015 22:46:16 -0700 Subject: [PATCH] 1.7.0 Chain() --- README.md | 15 ++++++++ bottleneck.js | 84 ++++++++++++++++++++++++------------------- bottleneck.min.js | 2 +- bower.json | 2 +- lib/Bottleneck.js | 36 ++++++++++++------- lib/Cluster.js | 46 ++++++++++++------------ lib/index.js | 2 +- package.json | 2 +- src/Bottleneck.coffee | 6 +++- 9 files changed, 119 insertions(+), 76 deletions(-) diff --git a/README.md b/README.md index 83fa6fa..e758408 100644 --- a/README.md +++ b/README.md @@ -144,6 +144,21 @@ limiter.incrementReservoir(incrementBy); If `reservoir` reaches `0`, no new requests will be executed until it is no more `0` +###chain() + +* `limiter` : If another limiter is passed, tasks that are ready to be executed will be submitted to that other limiter. *Default: `null` (none)* + +Suppose you have 2 types of tasks, A and B. They both have their own limiter with their own settings, but both must also follow a global limiter C: +```javascript +var limiterA = new Bottleneck(...some settings...); +var limiterB = new Bottleneck(...some different settings...); +var limiterC = new Bottleneck(...some global settings...); +limiterA.chain(limiterC); +limiterB.chain(limiterC); +// Requests submitted to limiterA must follow the A and C rate limits. +// Requests submitted to limiterB must follow the B and C rate limits. +// Requests submitted to limiterC must follow the C rate limits. +``` ##Execution guarantee diff --git a/bottleneck.js b/bottleneck.js index e75780f..2ba4241 100644 --- a/bottleneck.js +++ b/bottleneck.js @@ -1,8 +1,8 @@ (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0); }; @@ -45,18 +51,24 @@ done = false; index = -1 + this._timeouts.push(setTimeout((function(_this) { return function() { - return next.task.apply({}, next.args.concat(function() { - var _ref; + var completed; + completed = function() { + var ref; if (!done) { done = true; delete _this._timeouts[index]; _this._nbRunning--; _this._tryToRun(); if (!_this.interrupt) { - return (_ref = next.cb) != null ? _ref.apply({}, Array.prototype.slice.call(arguments, 0)) : void 0; + return (ref = next.cb) != null ? ref.apply({}, Array.prototype.slice.call(arguments, 0)) : void 0; } } - })); + }; + if (_this.limiter != null) { + return _this.limiter.submit.apply(_this.limiter, Array.prototype.concat.call(next.task, next.args, completed)); + } else { + return next.task.apply({}, next.args.concat(completed)); + } }; })(this), wait)); return true; @@ -66,8 +78,8 @@ }; Bottleneck.prototype.submit = function() { - 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++]; + var args, cb, i, reachedHighWaterMark, task; + 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 (this.strategy === Bottleneck.prototype.strategy.BLOCK && (reachedHighWaterMark || this._unblockTime >= Date.now())) { this._unblockTime = Date.now() + this.penalty; @@ -119,11 +131,11 @@ }; Bottleneck.prototype.stopAll = function(interrupt) { - var a, _i, _len, _ref; + var a, i, len, ref; this.interrupt = interrupt != null ? interrupt : this.interrupt; - _ref = this._timeouts; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - a = _ref[_i]; + ref = this._timeouts; + for (i = 0, len = ref.length; i < len; i++) { + a = ref[i]; clearTimeout(a); } this._tryToRun = function() {}; @@ -144,59 +156,59 @@ }).call(this); },{"./Cluster":2}],2:[function(require,module,exports){ -// Generated by CoffeeScript 1.8.0 +// Generated by CoffeeScript 1.9.1 (function() { var Cluster, - __hasProp = {}.hasOwnProperty; + hasProp = {}.hasOwnProperty; Cluster = (function() { function Cluster(maxNb, minTime, highWater, strategy) { - var _base; + var base; this.maxNb = maxNb; this.minTime = minTime; this.highWater = highWater; this.strategy = strategy; this.limiters = {}; this.Bottleneck = require("./Bottleneck"); - if (typeof (_base = setInterval((function(_this) { + if (typeof (base = setInterval((function(_this) { return function() { - var k, time, v, _ref, _results; + var k, ref, results, time, v; time = Date.now(); - _ref = _this.limiters; - _results = []; - for (k in _ref) { - v = _ref[k]; + ref = _this.limiters; + results = []; + for (k in ref) { + v = ref[k]; if ((v._nextRequest + (60 * 1000 * 5)) < time) { - _results.push(delete _this.limiters[k]); + results.push(delete _this.limiters[k]); } else { - _results.push(void 0); + results.push(void 0); } } - return _results; + return results; }; })(this), 60 * 1000)).unref === "function") { - _base.unref(); + base.unref(); } } Cluster.prototype.key = function(key) { - var _ref; + var ref; if (key == null) { key = ""; } - return (_ref = this.limiters[key]) != null ? _ref : (this.limiters[key] = new this.Bottleneck(this.maxNb, this.minTime, this.highWater, this.strategy)); + return (ref = this.limiters[key]) != null ? ref : (this.limiters[key] = new this.Bottleneck(this.maxNb, this.minTime, this.highWater, this.strategy)); }; Cluster.prototype.all = function(cb) { - var k, v, _ref, _results; - _ref = this.limiters; - _results = []; - for (k in _ref) { - if (!__hasProp.call(_ref, k)) continue; - v = _ref[k]; - _results.push(cb(v)); + var k, ref, results, v; + ref = this.limiters; + results = []; + for (k in ref) { + if (!hasProp.call(ref, k)) continue; + v = ref[k]; + results.push(cb(v)); } - return _results; + return results; }; Cluster.prototype.keys = function() { @@ -213,7 +225,7 @@ },{"./Bottleneck":1}],3:[function(require,module,exports){ (function (global){ -// Generated by CoffeeScript 1.8.0 +// Generated by CoffeeScript 1.9.1 (function() { module.exports = require("./Bottleneck"); diff --git a/bottleneck.min.js b/bottleneck.min.js index 0e0e76a..9c404cf 100644 --- a/bottleneck.min.js +++ b/bottleneck.min.js @@ -1 +1 @@ -(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o0)};Bottleneck.prototype._tryToRun=function(){var done,index,next,wait;if((this._nbRunning0&&(this.reservoir==null||this.reservoir>0)){this._nbRunning++;if(this.reservoir!=null){this.reservoir--}wait=Math.max(this._nextRequest-Date.now(),0);this._nextRequest=Date.now()+wait+this.minTime;next=this._queue.shift();done=false;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];_this._nbRunning--;_this._tryToRun();if(!_this.interrupt){return(_ref=next.cb)!=null?_ref.apply({},Array.prototype.slice.call(arguments,0)):void 0}}}))}}(this),wait));return true}else{return false}};Bottleneck.prototype.submit=function(){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(this.strategy===Bottleneck.prototype.strategy.BLOCK&&(reachedHighWaterMark||this._unblockTime>=Date.now())){this._unblockTime=Date.now()+this.penalty;this._nextRequest=this._unblockTime+this.minTime;this._queue=[];return true}else 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});this._tryToRun();return reachedHighWaterMark};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;while(this._tryToRun()){}return this};Bottleneck.prototype.changePenalty=function(penalty){this.penalty=penalty!=null?penalty:this.penalty;return this};Bottleneck.prototype.changeReservoir=function(reservoir){this.reservoir=reservoir;while(this._tryToRun()){}return this};Bottleneck.prototype.incrementReservoir=function(incr){if(incr==null){incr=0}this.changeReservoir(this.reservoir+incr);return this};Bottleneck.prototype.stopAll=function(interrupt){var a,_i,_len,_ref;this.interrupt=interrupt!=null?interrupt:this.interrupt;_ref=this._timeouts;for(_i=0,_len=_ref.length;_i<_len;_i++){a=_ref[_i];clearTimeout(a)}this._tryToRun=function(){};this.submit=function(){return false};return this.check=function(){return false}};return Bottleneck}();module.exports=Bottleneck}).call(this)},{"./Cluster":2}],2:[function(require,module,exports){(function(){var Cluster,__hasProp={}.hasOwnProperty;Cluster=function(){function Cluster(maxNb,minTime,highWater,strategy){var _base;this.maxNb=maxNb;this.minTime=minTime;this.highWater=highWater;this.strategy=strategy;this.limiters={};this.Bottleneck=require("./Bottleneck");if(typeof(_base=setInterval(function(_this){return function(){var k,time,v,_ref,_results;time=Date.now();_ref=_this.limiters;_results=[];for(k in _ref){v=_ref[k];if(v._nextRequest+60*1e3*50)};Bottleneck.prototype._tryToRun=function(){var done,index,next,wait;if((this._nbRunning0&&(this.reservoir==null||this.reservoir>0)){this._nbRunning++;if(this.reservoir!=null){this.reservoir--}wait=Math.max(this._nextRequest-Date.now(),0);this._nextRequest=Date.now()+wait+this.minTime;next=this._queue.shift();done=false;index=-1+this._timeouts.push(setTimeout(function(_this){return function(){var completed;completed=function(){var ref;if(!done){done=true;delete _this._timeouts[index];_this._nbRunning--;_this._tryToRun();if(!_this.interrupt){return(ref=next.cb)!=null?ref.apply({},Array.prototype.slice.call(arguments,0)):void 0}}};if(_this.limiter!=null){return _this.limiter.submit.apply(_this.limiter,Array.prototype.concat.call(next.task,next.args,completed))}else{return next.task.apply({},next.args.concat(completed))}}}(this),wait));return true}else{return false}};Bottleneck.prototype.submit=function(){var args,cb,i,reachedHighWaterMark,task;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(this.strategy===Bottleneck.prototype.strategy.BLOCK&&(reachedHighWaterMark||this._unblockTime>=Date.now())){this._unblockTime=Date.now()+this.penalty;this._nextRequest=this._unblockTime+this.minTime;this._queue=[];return true}else 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});this._tryToRun();return reachedHighWaterMark};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;while(this._tryToRun()){}return this};Bottleneck.prototype.changePenalty=function(penalty){this.penalty=penalty!=null?penalty:this.penalty;return this};Bottleneck.prototype.changeReservoir=function(reservoir){this.reservoir=reservoir;while(this._tryToRun()){}return this};Bottleneck.prototype.incrementReservoir=function(incr){if(incr==null){incr=0}this.changeReservoir(this.reservoir+incr);return this};Bottleneck.prototype.stopAll=function(interrupt){var a,i,len,ref;this.interrupt=interrupt!=null?interrupt:this.interrupt;ref=this._timeouts;for(i=0,len=ref.length;i" diff --git a/lib/Bottleneck.js b/lib/Bottleneck.js index 2e415f2..73b6e87 100644 --- a/lib/Bottleneck.js +++ b/lib/Bottleneck.js @@ -1,7 +1,7 @@ -// Generated by CoffeeScript 1.8.0 +// Generated by CoffeeScript 1.9.1 (function() { var Bottleneck, - __slice = [].slice; + slice = [].slice; Bottleneck = (function() { Bottleneck.strategy = Bottleneck.prototype.strategy = { @@ -25,8 +25,14 @@ this.penalty = (15 * this.minTime) || 5000; this.interrupt = false; this.reservoir = null; + this.limiter = null; } + Bottleneck.prototype.chain = function(limiter) { + this.limiter = limiter; + return this; + }; + Bottleneck.prototype.check = function() { return (this._nbRunning < this.maxNb || this.maxNb <= 0) && (this._nextRequest - Date.now()) <= 0 && ((this.reservoir == null) || this.reservoir > 0); }; @@ -44,18 +50,24 @@ done = false; index = -1 + this._timeouts.push(setTimeout((function(_this) { return function() { - return next.task.apply({}, next.args.concat(function() { - var _ref; + var completed; + completed = function() { + var ref; if (!done) { done = true; delete _this._timeouts[index]; _this._nbRunning--; _this._tryToRun(); if (!_this.interrupt) { - return (_ref = next.cb) != null ? _ref.apply({}, Array.prototype.slice.call(arguments, 0)) : void 0; + return (ref = next.cb) != null ? ref.apply({}, Array.prototype.slice.call(arguments, 0)) : void 0; } } - })); + }; + if (_this.limiter != null) { + return _this.limiter.submit.apply(_this.limiter, Array.prototype.concat.call(next.task, next.args, completed)); + } else { + return next.task.apply({}, next.args.concat(completed)); + } }; })(this), wait)); return true; @@ -65,8 +77,8 @@ }; Bottleneck.prototype.submit = function() { - 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++]; + var args, cb, i, reachedHighWaterMark, task; + 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 (this.strategy === Bottleneck.prototype.strategy.BLOCK && (reachedHighWaterMark || this._unblockTime >= Date.now())) { this._unblockTime = Date.now() + this.penalty; @@ -118,11 +130,11 @@ }; Bottleneck.prototype.stopAll = function(interrupt) { - var a, _i, _len, _ref; + var a, i, len, ref; this.interrupt = interrupt != null ? interrupt : this.interrupt; - _ref = this._timeouts; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - a = _ref[_i]; + ref = this._timeouts; + for (i = 0, len = ref.length; i < len; i++) { + a = ref[i]; clearTimeout(a); } this._tryToRun = function() {}; diff --git a/lib/Cluster.js b/lib/Cluster.js index a375ed9..817cac9 100644 --- a/lib/Cluster.js +++ b/lib/Cluster.js @@ -1,56 +1,56 @@ -// Generated by CoffeeScript 1.8.0 +// Generated by CoffeeScript 1.9.1 (function() { var Cluster, - __hasProp = {}.hasOwnProperty; + hasProp = {}.hasOwnProperty; Cluster = (function() { function Cluster(maxNb, minTime, highWater, strategy) { - var _base; + var base; this.maxNb = maxNb; this.minTime = minTime; this.highWater = highWater; this.strategy = strategy; this.limiters = {}; this.Bottleneck = require("./Bottleneck"); - if (typeof (_base = setInterval((function(_this) { + if (typeof (base = setInterval((function(_this) { return function() { - var k, time, v, _ref, _results; + var k, ref, results, time, v; time = Date.now(); - _ref = _this.limiters; - _results = []; - for (k in _ref) { - v = _ref[k]; + ref = _this.limiters; + results = []; + for (k in ref) { + v = ref[k]; if ((v._nextRequest + (60 * 1000 * 5)) < time) { - _results.push(delete _this.limiters[k]); + results.push(delete _this.limiters[k]); } else { - _results.push(void 0); + results.push(void 0); } } - return _results; + return results; }; })(this), 60 * 1000)).unref === "function") { - _base.unref(); + base.unref(); } } Cluster.prototype.key = function(key) { - var _ref; + var ref; if (key == null) { key = ""; } - return (_ref = this.limiters[key]) != null ? _ref : (this.limiters[key] = new this.Bottleneck(this.maxNb, this.minTime, this.highWater, this.strategy)); + return (ref = this.limiters[key]) != null ? ref : (this.limiters[key] = new this.Bottleneck(this.maxNb, this.minTime, this.highWater, this.strategy)); }; Cluster.prototype.all = function(cb) { - var k, v, _ref, _results; - _ref = this.limiters; - _results = []; - for (k in _ref) { - if (!__hasProp.call(_ref, k)) continue; - v = _ref[k]; - _results.push(cb(v)); + var k, ref, results, v; + ref = this.limiters; + results = []; + for (k in ref) { + if (!hasProp.call(ref, k)) continue; + v = ref[k]; + results.push(cb(v)); } - return _results; + return results; }; Cluster.prototype.keys = function() { diff --git a/lib/index.js b/lib/index.js index 5672032..f91d08d 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.8.0 +// Generated by CoffeeScript 1.9.1 (function() { module.exports = require("./Bottleneck"); diff --git a/package.json b/package.json index bed0568..587b5d2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bottleneck", - "version": "1.6.0", + "version": "1.7.0", "description": "Async rate limiter", "main": "lib/index.js", "scripts": { diff --git a/src/Bottleneck.coffee b/src/Bottleneck.coffee index 589baf4..53f3ed9 100644 --- a/src/Bottleneck.coffee +++ b/src/Bottleneck.coffee @@ -10,6 +10,8 @@ class Bottleneck @penalty = (15 * @minTime) or 5000 @interrupt = false @reservoir = null + @limiter = null + chain: (@limiter) -> @ check: -> (@_nbRunning < @maxNb or @maxNb <= 0) and (@_nextRequest-Date.now()) <= 0 and (not @reservoir? or @reservoir > 0) _tryToRun: -> if (@_nbRunning < @maxNb or @maxNb <= 0) and @_queue.length > 0 and (not @reservoir? or @reservoir > 0) @@ -20,13 +22,15 @@ class Bottleneck next = @_queue.shift() done = false index = -1 + @_timeouts.push setTimeout => - next.task.apply {}, next.args.concat => + completed = => if not done done = true delete @_timeouts[index] @_nbRunning-- @_tryToRun() if not @interrupt then next.cb?.apply {}, Array::slice.call arguments, 0 + if @limiter? then @limiter.submit.apply @limiter, Array::concat.call next.task, next.args, completed + else next.task.apply {}, next.args.concat completed , wait true else false