├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── index.js ├── package.json └── test └── throttle.test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.8 4 | - 0.10 5 | notifications: 6 | email: 7 | recipients: 8 | - brianloveswords@gmail.com -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Brian J. Brennan 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included 12 | in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # throttle-function [![Build Status](https://secure.travis-ci.org/brianloveswords/throttle-function.png?branch=master)](http://travis-ci.org/brianloveswords/throttle-function) 2 | 3 | Takes a function, returns a function that will can only be called a certain amount of times per second. 4 | 5 | ## Install 6 | 7 | ```bash 8 | $ npm install throttle-function 9 | ``` 10 | 11 | ## Usage 12 | 13 | ```js 14 | const throttle = require('throttle-function') 15 | const api = require('./api') 16 | 17 | // call a maximum of 180 times per 15 minute window 18 | var getWhatever = throttle(api.getWhatever, { 19 | window: 15 * 60, // window is in seconds 20 | limit: 180 21 | }) 22 | 23 | // you can also use straight up milliseconds 24 | getWhatever = throttle(api.getWhatever, 5000) 25 | 26 | // this will fire off every 5 seconds instead of immediately 27 | getWhatever() 28 | getWhatever() 29 | getWhatever() 30 | ``` 31 | 32 | ## License 33 | MIT 34 | 35 | ``` 36 | Copyright (c) 2013 Brian J. Brennan 37 | 38 | Permission is hereby granted, free of charge, to any person obtaining a 39 | copy of this software and associated documentation files (the 40 | "Software"), to deal in the Software without restriction, including 41 | without limitation the rights to use, copy, modify, merge, publish, 42 | distribute, sublicense, and/or sell copies of the Software, and to 43 | permit persons to whom the Software is furnished to do so, subject to 44 | the following conditions: 45 | 46 | The above copyright notice and this permission notice shall be included 47 | in all copies or substantial portions of the Software. 48 | 49 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 50 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 51 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 52 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 53 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 54 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 55 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 56 | ``` 57 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = throttle 2 | 3 | function throttle(fn, opts) { 4 | opts = opts || {} 5 | var timer 6 | var queue = [] 7 | const errMsg = 'Must pass options or milliseconds as second argument' 8 | if (!opts) 9 | throw new Error(errMsg) 10 | 11 | var msBetweenCalls 12 | 13 | if (typeof opts == 'string') 14 | msBetweenCalls = Number(opts) 15 | 16 | else if (typeof opts == 'number') 17 | msBetweenCalls = opts 18 | 19 | else { 20 | const window = opts.window || 1 21 | const limit = opts.limit || 1 22 | const exact = opts.exact || false 23 | msBetweenCalls = Math.ceil((window / limit) * 1000) 24 | } 25 | 26 | if (isNaN(msBetweenCalls)) 27 | throw new Error(errMsg) 28 | 29 | function enqueue(args) { 30 | return queue.push(args) 31 | } 32 | 33 | function dequeue() { 34 | return queue.shift() 35 | } 36 | 37 | function kickQueue() { 38 | if (timer) return timer 39 | timer = setInterval(runQueue, msBetweenCalls) 40 | return timer 41 | } 42 | 43 | function runQueue() { 44 | const args = dequeue() 45 | 46 | if (queue.length == 0 || !args) { 47 | clearInterval(timer) 48 | timer = null 49 | } 50 | 51 | const result = fn.apply(null, args) 52 | return result 53 | } 54 | 55 | const throttled = function () { 56 | const args = [].slice.call(arguments) 57 | const position = enqueue(args) 58 | const timer = kickQueue() 59 | 60 | return { 61 | position: position, 62 | queuedAt: Date.now(), 63 | timeUntilCall: position * msBetweenCalls 64 | } 65 | } 66 | 67 | return throttled 68 | } 69 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "throttle-function", 3 | "version": "0.1.1", 4 | "description": "Takes a function, returns a function that will can only be called a certain amount of times per second.", 5 | "main": "index.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "tap test/*.test.js" 11 | }, 12 | "author": "Brian J Brennan", 13 | "license": "MIT", 14 | "devDependencies": { 15 | "tap": "~0.4.6" 16 | }, 17 | "dependencies": {}, 18 | "repository": { 19 | "type": "git", 20 | "url": "git://github.com/brianloveswords/throttle-function.git" 21 | }, 22 | "keywords": [ 23 | "throttle", 24 | "function" 25 | ], 26 | "bugs": { 27 | "url": "https://github.com/brianloveswords/throttle-function/issues" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/throttle.test.js: -------------------------------------------------------------------------------- 1 | const throttle = require('..') 2 | const test = require('tap').test 3 | 4 | test('throttling', function (t) { 5 | var first, second, third 6 | 7 | const opts = { window: 1, limit: 3 } 8 | const getDate = throttle(function getDate(callback) { 9 | process.nextTick(function () { return callback(Date.now()) }) 10 | }, opts) 11 | 12 | const ops = [ 13 | getDate(function _init(date) { 14 | console.log('called init', date) 15 | proceed() 16 | }), 17 | getDate(function _first(date) { 18 | first = date; 19 | console.log('called first', first) 20 | proceed() 21 | }), 22 | getDate(function _second(date) { 23 | second = date; 24 | console.log('called second', second) 25 | proceed() 26 | }), 27 | getDate(function _third(date) { 28 | third = date; 29 | console.log('called third', third) 30 | proceed() 31 | }), 32 | ] 33 | 34 | var waiting = ops.length 35 | console.dir(ops) 36 | 37 | function proceed() { 38 | if (--waiting > 0) return 39 | 40 | const diff1 = second - first 41 | const diff2 = third - second 42 | 43 | t.notEqual(first, second, 'should not be the same') 44 | t.ok(diff1 >= 166, 'diff1 should be at least 166 ms') 45 | t.ok(diff2 >= 166, 'diff2 should be at least 166 ms') 46 | t.end() 47 | } 48 | }) 49 | 50 | test('throttling, straight up milliseconds', function (t) { 51 | var first, second 52 | 53 | const getDate = throttle(function getDate(callback) { 54 | process.nextTick(function () { return callback(Date.now()) }) 55 | }, 300) 56 | 57 | const ops = [ 58 | getDate(function _first(date) { 59 | first = date; 60 | console.log('called first', first) 61 | proceed() 62 | }), 63 | getDate(function _second(date) { 64 | second = date; 65 | console.log('called second', second) 66 | proceed() 67 | }), 68 | ] 69 | 70 | var waiting = ops.length 71 | console.dir(ops) 72 | 73 | function proceed() { 74 | if (--waiting > 0) return 75 | 76 | const diff = second - first 77 | 78 | t.notEqual(first, second, 'should not be the same') 79 | t.ok(diff >= 300, 'diff should be at least 300 ms') 80 | t.end() 81 | } 82 | }) 83 | --------------------------------------------------------------------------------