rate-keeper
Version:
A lightweight utility for easily adding rate limiting to functions, ideal for preventing API rate limit violations.
88 lines • 3.27 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.DropPolicy = void 0;
exports.default = RateKeeper;
const globalRateData = new Map();
/**
* @enum {DropPolicy} Defines the behavior of the queue when the maximum size has been reached.
*/
var DropPolicy;
(function (DropPolicy) {
DropPolicy[DropPolicy["Reject"] = 0] = "Reject";
DropPolicy[DropPolicy["DropOldest"] = 1] = "DropOldest";
})(DropPolicy || (exports.DropPolicy = DropPolicy = {}));
class LimitData {
constructor(settings) {
this.queue = [];
this.timer = null;
this.settings = settings;
}
}
function getRateData(settings) {
const { id } = settings;
if (globalRateData.has(id)) {
return globalRateData.get(id);
}
else {
const newLimitData = new LimitData(settings);
globalRateData.set(id, newLimitData);
return newLimitData;
}
}
/**
* @param {(...args: Args) => Result} action The action to be rate-limited.
* @param {number} rateLimit The minimum interval in milliseconds between each execution.
* @param {QueueSettings} settings Optional. Queue settings for rate limiting and execution.
* @returns {(...args: Args) => CancelablePromise<Result>} An asynchronous function that executes the action and returns a promise with the result and a cancel method.
*/
function RateKeeper(action, rateLimit, settings = { id: 0 }) {
const limitData = settings.id === 0 ? new LimitData(settings) : getRateData(settings);
function processQueue() {
const next = limitData.queue.shift();
if (next) {
next.action();
}
if (limitData.queue.length === 0 && limitData.timer !== null) {
clearInterval(limitData.timer);
limitData.timer = null;
}
}
function publicFunc(...args) {
var _a;
const { maxQueueSize, dropPolicy } = limitData.settings;
let resolve;
let reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
const actionEntry = {
action: () => resolve(action(...args)),
reject: (reason) => { reject(reason); },
id: settings.id
};
promise.cancel = (reason) => {
const index = limitData.queue.indexOf(actionEntry);
if (index !== -1) {
limitData.queue.splice(index, 1);
actionEntry.reject(reason || new Error("Cancelled by user."));
}
};
if (maxQueueSize !== undefined && limitData.queue.length >= maxQueueSize) {
if (dropPolicy === DropPolicy.Reject) {
return Promise.reject(new Error("Queue is at max capacity."));
}
else if (dropPolicy === DropPolicy.DropOldest) {
(_a = limitData.queue.shift()) === null || _a === void 0 ? void 0 : _a.reject(new Error("Queue is at max capacity."));
}
}
limitData.queue.push(actionEntry);
if (limitData.timer === null) {
processQueue();
limitData.timer = setInterval(processQueue, rateLimit);
}
return promise;
}
return publicFunc;
}
//# sourceMappingURL=index.js.map