Skip to content

Instantly share code, notes, and snippets.

@jwulf
Created December 1, 2020 01:03
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jwulf/aec69da806a721b20a3e346e62eeb23b to your computer and use it in GitHub Desktop.
Save jwulf/aec69da806a721b20a3e346e62eeb23b to your computer and use it in GitHub Desktop.
A naive functional rate limiter with an absolute limit
interface QueuedTask<T> {
task: () => T;
promise: {
resolve: (res: any) => void;
reject: (err: any) => void;
};
}
interface RateLimitedTask<T> {
task: () => T;
/**
* Default: false. Set true to allow this to be bumped by other operations.
* Use this, for example, for non-UI background async tasks.
*/
preemptible?: boolean;
}
/**
* Rate limit operations, for example: to avoid saturating an API with calls
*/
export class RateLimiter {
private debounceMs: number;
private priorityQueue: QueuedTask<any>[] = [];
private preemptibleQueue: QueuedTask<any>[] = [];
private rateLimiting?: NodeJS.Timeout;
/**
*
* @param rateLimitToMs minimum number of milliseconds between operations
*/
constructor(rateLimitToMs: number) {
this.debounceMs = rateLimitToMs;
}
/**
*
* @param req {RateLimitedTask}
*/
runRateLimited<T>(req: RateLimitedTask<T>): Promise<T> {
const result = new Promise<T>((resolve, reject) => {
const queue = req.preemptible
? this.preemptibleQueue
: this.priorityQueue;
queue.push({
task: req.task,
promise: { resolve, reject },
});
});
this.scheduleNextTask();
return result;
}
private scheduleNextTask() {
if (!this.rateLimiting) {
this.runImmediately();
}
}
private runImmediately() {
const toRun: QueuedTask<any> | undefined = this.priorityQueue.length
? this.priorityQueue.pop()
: this.preemptibleQueue.pop();
if (!toRun) {
return;
}
const hasQueuedTasks =
!!this.priorityQueue.length || !!this.preemptibleQueue.length;
if (hasQueuedTasks) {
this.rateLimiting = setTimeout(() => {
this.rateLimiting = undefined;
this.runImmediately();
}, this.debounceMs);
}
const promise = toRun.promise;
toRun.task().then(promise.resolve).catch(promise.reject);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment