Skip to content

Instantly share code, notes, and snippets.

@luciferous
Created May 27, 2011 03:55
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save luciferous/994612 to your computer and use it in GitHub Desktop.
Save luciferous/994612 to your computer and use it in GitHub Desktop.
Rate Limiter in Javascript

How to use

Let's say you want to limit some event to 5 occurrences every 20 seconds, you would set up the RateLimit like so:

var limit = new RateLimit(20); // Initialize a 20 second ticklog

// For events that we initiate...
if (limit.count('myevent_id') <= 5)) {
    // Do some stuff
    limit.tick('myevent_id');
} else {
    // Over the limit!
}

// Or events that we respond to...
if (limit.count('someevent_id') > 5) {
    // Respond with failure
} else {
    // Respond with success!
}

Technical details

You could run this forever and ever and the ticklog would remain the same size because it's a fixed length list. As we add new entries to the list, the index wraps around to the beginning (i.e. index % ticklog.length). Every call to count iterates over this fixed length list and performs a dictionary look up for tick count indexed by id.

/**
* Constructor for RateLimit objects
* @constructor
* @param {number} [history=5] The size of the tick history
*/
function RateLimit(history) {
if (!(this instanceof RateLimit)) return new RateLimit(history);
history = history || 5;
/**
* The time this RateLimit object was created
* @type number
*/
this.created = parseInt(new Date().getTime() / 1000);
/**
* Time of last tick
* @type number
*/
this.lasttick = 0;
/**
* Stores a bunch of ticks
* @type array
*/
this.ticklog = [];
for (var i = 0; i < history; i++) {
this.ticklog.push([0, {}]);
}
}
RateLimit.prototype = {
/**
* Checks if the occurrences for this id are within the limit and if true,
* does a tick and returns true, otherwise returns false.
* @param id Any kind of identifier
* @param {number} limit The max occurrences for this identifier
* @param {number} [now] The time right now
*/
tick: function(id, now) {
if (!id) return;
now = now || parseInt(new Date().getTime() / 1000) - this.created;
this.lasttick = now;
var index = now % this.ticklog.length;
var entry = (this.ticklog[index] &&
this.ticklog[index][0] == now)
? this.ticklog[index]
: this.ticklog[index] = [now, {}];
var timestamp = entry[0],
tickcount = entry[1];
tickcount[id] = tickcount[id] ? tickcount[id] + 1: 1;
return;
},
/**
* Checks if the occurrences for this id are within the limit and if true,
* does a tick and returns true, otherwise returns false.
* @param id Any kind of identifier
* @param {number} limit The max occurrences for this identifier
* @param {number} [now] The time right now
* @return {number} The number of ticks by a given id
*/
count: function(id, now) {
if (!id) return;
now = now || parseInt(new Date().getTime() / 1000) - this.created;
var count = 0;
for (var i = 0; i < this.ticklog.length; i++) {
var timestamp = this.ticklog[i][0],
tickcount = this.ticklog[i][1];
if (now < timestamp ||
now - timestamp >= this.ticklog.length) {
continue;
}
if (tickcount[id]) {
count += tickcount[id];
}
}
return count;
},
/**
* Checks if the occurrences for this id are within the limit and if true,
* does a tick and returns true, otherwise returns false.
* @param id Any kind of identifier
* @param {number} limit The max occurrences for this identifier
* @param {number} [now] The time right now
* @return {boolean} True if within the limit
*/
execute: function(id, limit, now) {
if (!id) return;
if (!limit) return;
now = now || parseInt(new Date().getTime() / 1000) - this.created;
var success = this.count(id, now) < limit;
if (success) {
this.tick(id, now);
}
return success;
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment