84 lines
2.5 KiB
JavaScript
Raw Normal View History

2023-11-29 18:23:54 +03:00
"use strict";
const util = require("util");
const Base = require("../structures/Base");
/**
* Ratelimit requests and release in sequence
* TODO: add latencyref
* @prop {Number} limit How many tokens the bucket can consume in the current interval
* @prop {Boolean} processing Whether the queue is being processed
* @prop {Number} remaining How many tokens the bucket has left in the current interval
* @prop {Number} reset Timestamp of next reset
*/
class SequentialBucket {
/**
* Construct a SequentialBucket
* @arg {Number} limit The max number of tokens the bucket can consume per interval
* @arg {Object} [latencyRef] An object
* @arg {Number} latencyRef.latency Interval between consuming tokens
*/
constructor(limit, latencyRef = {latency: 0}) {
this.limit = this.remaining = limit;
this.reset = 0;
this.processing = false;
this.latencyRef = latencyRef;
this._queue = [];
}
check(override) {
if(this._queue.length === 0) {
if(this.processing) {
clearTimeout(this.processing);
this.processing = false;
}
return;
}
if(this.processing && !override) {
return;
}
const now = Date.now();
const offset = this.latencyRef.latency;
if(!this.reset || this.reset < now - offset) {
this.reset = now - offset;
this.remaining = this.limit;
}
this.last = now;
if(this.remaining <= 0) {
this.processing = setTimeout(() => {
this.processing = false;
this.check(true);
}, Math.max(0, (this.reset || 0) - now + offset) + 1);
return;
}
--this.remaining;
this.processing = true;
this._queue.shift()(() => {
if(this._queue.length > 0) {
this.check(true);
} else {
this.processing = false;
}
});
}
/**
* Queue something in the SequentialBucket
* @arg {Function} func A function to call when a token can be consumed. The function will be passed a callback argument, which must be called to allow the bucket to continue to work
*/
queue(func, short) {
if(short) {
this._queue.unshift(func);
} else {
this._queue.push(func);
}
this.check();
}
[util.inspect.custom]() {
return Base.prototype[util.inspect.custom].call(this);
}
}
module.exports = SequentialBucket;