├── .gitignore ├── .travis.yml ├── README.md ├── index.js ├── package.json └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - '0.10' 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | node-function-rate-limit 2 | -------------- 3 | 4 | [![NPM version][npm-image]][npm-url] 5 | [![Build status][travis-image]][travis-url] 6 | 7 | Limit the execution rate of any function. 8 | 9 | install 10 | --------- 11 | 12 | with [npm](https://npmjs.org) 13 | 14 | ```bash 15 | npm install function-rate-limit 16 | ``` 17 | 18 | api 19 | ---- 20 | 21 | ### rateLimit(limitCount, limitInterval, function); 22 | 23 | returns a rate limited function which should be called instead of the `function` passed to `rateLimit` 24 | 25 | * _limitCount_ - the number of times per `limitInterval` to limit execution of `function` 26 | * _limitInterval_ - the duration of time during which to limit execution of `function` specified in ms 27 | * _function_ - the function which should be rate limited 28 | 29 | `function` will be called up to `limitCount` times during `limitInterval` including bursting. 30 | 31 | Example 32 | ------- 33 | 34 | ```javascript 35 | var rateLimit = require('function-rate-limit'); 36 | 37 | // limit to 2 executions per 1000ms 38 | var start = Date.now() 39 | var fn = rateLimit(2, 1000, function (x) { 40 | console.log('%s ms - %s', Date.now() - start, x); 41 | }); 42 | 43 | for (var y = 0; y < 10; y++) { 44 | fn(y); 45 | } 46 | ``` 47 | 48 | results in: 49 | 50 | ```bash 51 | 10 ms - 0 52 | 11 ms - 1 53 | 1004 ms - 2 54 | 1012 ms - 3 55 | 2008 ms - 4 56 | 2013 ms - 5 57 | 3010 ms - 6 58 | 3014 ms - 7 59 | 4017 ms - 8 60 | 4017 ms - 9 61 | ``` 62 | 63 | pre 1.x behavior 64 | ------------- 65 | 66 | Prior to version 1.x.x, this module behaved as a throttle module. `function` would be invoked only one time per `limitCount/limitInterval` with no bursting. If you need this functionality again and do not want bursting, see the `lodash.throttle` module. 67 | 68 | License 69 | ---------- 70 | 71 | ### The MIT License (MIT) 72 | 73 | 74 | Copyright (c) 2012 Daniel L. VerWeire 75 | 76 | Permission is hereby granted, free of charge, to any person obtaining 77 | a copy of this software and associated documentation files (the 78 | "Software"), to deal in the Software without restriction, including 79 | without limitation the rights to use, copy, modify, merge, publish, 80 | distribute, sublicense, and/or sell copies of the Software, and to 81 | permit persons to whom the Software is furnished to do so, subject to 82 | the following conditions: 83 | 84 | The above copyright notice and this permission notice shall be 85 | included in all copies or substantial portions of the Software. 86 | 87 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 88 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 89 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 90 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 91 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 92 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 93 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 94 | 95 | [npm-image]: https://img.shields.io/npm/v/function-rate-limit.svg?style=flat-square 96 | [npm-url]: https://npmjs.org/package/function-rate-limit 97 | [travis-image]: https://travis-ci.org/wankdanker/node-function-rate-limit.svg?style=flat-square 98 | [travis-url]: https://travis-ci.org/wankdanker/node-function-rate-limit 99 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = rateLimit; 2 | 3 | function rateLimit(limitCount, limitInterval, fn) { 4 | var fifo = []; 5 | 6 | // count starts at limit 7 | // each call of `fn` decrements the count 8 | // it is incremented after limitInterval 9 | var count = limitCount; 10 | 11 | function call_next(args) { 12 | setTimeout(function() { 13 | if (fifo.length > 0) { 14 | call_next(); 15 | } 16 | else { 17 | count = count + 1; 18 | } 19 | }, limitInterval); 20 | 21 | var call_args = fifo.shift(); 22 | 23 | // if there is no next item in the queue 24 | // and we were called with args, trigger function immediately 25 | if (!call_args && args) { 26 | fn.apply(args[0], args[1]); 27 | return; 28 | } 29 | 30 | fn.apply(call_args[0], call_args[1]); 31 | } 32 | 33 | return function rate_limited_function() { 34 | var ctx = this; 35 | var args = Array.prototype.slice.call(arguments); 36 | if (count <= 0) { 37 | fifo.push([ctx, args]); 38 | return; 39 | } 40 | 41 | count = count - 1; 42 | call_next([ctx, args]); 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "function-rate-limit", 3 | "version": "1.1.0", 4 | "description": "Limit the execution rate of any function", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha --ui qunit --reporter spec test.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git@github.com:wankdanker/node-function-rate-limit.git" 12 | }, 13 | "keywords": [ 14 | "rate", 15 | "limit", 16 | "function" 17 | ], 18 | "devDependencies": { 19 | "after": "^0.8.1", 20 | "mocha": "2.2.1" 21 | }, 22 | "author": "Dan VerWeire", 23 | "license": "MIT" 24 | } 25 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var after = require('after'); 3 | 4 | var rateLimit = require('./'); 5 | 6 | test('should only allow one call per interval', function (done) { 7 | var start = Date.now(); 8 | 9 | // time that passes is 400ms since the first call executes immediate 10 | var expected = [0, 100, 200, 300, 400]; 11 | var offsets = []; 12 | 13 | var trigger = after(5, function() { 14 | fuzzy_compare(expected, offsets) 15 | done(); 16 | }); 17 | 18 | var fn = rateLimit(1, 100, function() { 19 | offsets.push(Date.now() - start); 20 | trigger(); 21 | }); 22 | 23 | for (var i = 0; i < 5; ++i) { 24 | fn(i); 25 | } 26 | }); 27 | 28 | test('should allow for calls to burst', function (done) { 29 | var start = Date.now(); 30 | var expected = [0, 0, 100, 100, 200]; 31 | var offsets = []; 32 | 33 | // time that passes is 400ms since the first call executes immediate 34 | var trigger = after(5, function() { 35 | fuzzy_compare(expected, offsets) 36 | done(); 37 | }); 38 | 39 | var fn = rateLimit(2, 100, function() { 40 | offsets.push(Date.now() - start); 41 | trigger(); 42 | }); 43 | 44 | for (var i = 0; i < 5; ++i) { 45 | fn(i); 46 | } 47 | }); 48 | 49 | test('should preserve function context', function (done) { 50 | var start = Date.now(); 51 | 52 | // time that passes is 400ms since the first call executes immediate 53 | var expected = [0, 100, 200, 300, 400]; 54 | var offsets = []; 55 | 56 | var trigger = after(5, function() { 57 | fuzzy_compare(expected, offsets) 58 | done(); 59 | }); 60 | 61 | var fn = rateLimit(1, 100, function() { 62 | assert(this.foo === 'bar'); 63 | offsets.push(Date.now() - start); 64 | trigger(); 65 | }); 66 | 67 | for (var i = 0; i < 5; ++i) { 68 | fn.call({ foo: 'bar' }, i); 69 | } 70 | }); 71 | 72 | function fuzzy_compare(expected, actual) { 73 | assert.equal(expected.length, actual.length); 74 | 75 | expected.forEach(function(expected_value, idx) { 76 | var actual_val = actual[idx]; 77 | 78 | var diff = Math.abs(expected_value - actual_val); 79 | if (diff > 20) { 80 | throw new Error('actual and expected values differ too much: actual ' + actual_val + ' != ' + expected_value); 81 | } 82 | }); 83 | } 84 | --------------------------------------------------------------------------------