###JavaScript
This is a governed sliding window throttled queue that conforms to the published usage guidelines regarding request rate.
It works by executing queued callbacks at a nominal rate.
Similar to the C# version, but without threading.
It provides:
- maximum throughput for isolated bursts
- reliable execution for mass requests.
Usage:
// create a common Throttle
window.RequestThrottle = new Throttle(5000,30,15);
// route every request through the throttle
RequestThrottle.enqueue(function(){
/* do something*/
// but NO MATTER WHAT you need to signal when complete
// regardless of state, so get your exception handling hat on.
RequestThrottle.signal();
});
Throttle.js
Throttle = function(throttleWindowTime, throttleWindowCount, maxActiveRequests) {
/// <summary>
/// The purpose of Throttle is to queue requests in the interest of
/// preventing inadvertant DOS attacks.
///
/// This throttle is intended to work with a sliding window and a governor.
///
/// The criteria are - X requests per Y seconds while < z requests are pending.
/// where X: throttleWindowCount
/// Y: throttleWindowTime
/// X: maxActiveRequests
///
/// </summary>
/// <param name="throttleWindowTime" type="Number">The amount of time, in ms,
/// that defines the throttle window.<param>
/// <param name="throttleWindowCount" type="Number">The number of requests
/// allowed in any throttle window<param>
/// <param name="maxActiveRequests" type="Number">The maximum allowed number
/// of active request<param>
/// <remarks>
/// Not sure why I care anymore, but it just seems the thing to do.
///
/// If you have another idea, better or otherwise, please share.
/// </remarks>
var times = new Queue(),
requests = new Queue(),
active = 0,
windowTime = throttleWindowTime,
windowCount = throttleWindowCount,
maxActive = maxActiveRequests,
timer = false,
that = this;
var lastRequestTime = 0; // for debugging
this.processQueue = function() {
/// <summary>
/// there are several criteria that need to be met in order
/// for a request to be executed:
///
/// 1 - that the active request count < max allowed active count
///
/// 2 - that the difference in time between Now and the
/// throttleWindowCount-nth request is greater than
/// throttleWindowTime.
/// </summary>
if (requests.count() === 0 || (active === maxActive)) {
return;
};
var head = new Date().getTime();
var queueCount = times.count()
if (queueCount >= windowCount) {
// check the difference
var tail = times.peek() || 0;
if (head - tail < windowTime) {
// not time yet
return;
};
// we are a go, snap the tail off
times.dequeue();
}
// Metrics: the interval since last request
//var interval = head - lastRequestTime;
lastRequestTime = head;
var request = requests.dequeue();
if (typeof request == 'function') {
active++;
times.enqueue(head);
request();
}
};
this.signal = function() {
/// <summary>
/// This method simply decrements the active request count.
/// It simply MUST be called once for each request issued
/// regardless of the final status of the request.
/// This means that you MUST NOT swallow execution errors.
/// </summary>
active = active - 1;
if (active < 0) {
throw Error.invalidOperation("Request count negative.");
};
};
this.enqueue = function(callback) {
/// <summary>
/// Adds a callback to the throttle queue.
/// </summary>
/// <param name="callback" type="Function">The function to be executed in due time.<param>
requests.enqueue(callback);
};
this.clear = function() {
/// <summary>
/// Suspends processing and clears queues and counts.
/// </summary>
that.suspend();
times = new Queue();
requests = new Queue();
active = 0;
};
this.start = function(pollingInterval) {
/// <summary>
/// Resumes processing of the throttle queue at the specified
/// polling interval;
///
/// If the throttle is running, calls to this method are ignored.
/// </summary>
/// <param name="" type="Number">The desired polling interval in ms</param>
if (!timer) {
timer = window
.setInterval(that.processQueue, pollingInterval);
};
};
this.suspend = function() {
/// <summary>
/// Suspends processing of the throttle queue.
///
/// If the throttle is not running, calls to this method are ignored.
/// </summary>
if (timer) {
window.clearInterval(timer);
timer = false;
};
};
this.start(75);
};
function Queue() {
this._queue = [];
this._queueSpace = 0;
}
Queue.prototype = {
enqueue: function(element) {
this._queue.push(element);
},
dequeue: function() {
var element = undefined;
if (this._queue.length) {
element = this._queue[this._queueSpace];
if (++this._queueSpace * 2 >= this._queue.length) {
this._queue = this._queue.slice(this._queueSpace);
this._queueSpace = 0;
}
}
return element;
},
count: function() {
return this._queue.length - this._queueSpace;
},
peek: function() {
var element = undefined;
if (this._queue.length) element = this._queue[this._queueSpace];
return element;
}
};