export class PromiseRateLimiter { private callbacks: (() => Promise)[] = []; private callbackResults: T[] = []; private nextIndex: number = 0; private endPromise: Promise = null; private launched: boolean = false; private aborted: boolean = false; private resolveCallback: (value?: (PromiseLike | T[])) => void; private resolvedPromises: number = 0; private rejectCallback: (reason?: any) => void; private startedPromises: number = 0; public constructor(private maxConcurrentPromises: number) {} public addCallback(callback: () => Promise) { if (this.launched) { throw new Error("Cannot add callback after launching rate limiter"); } this.callbacks.push(callback); } public launch(): Promise { this.launched = true; this.endPromise = new Promise((resolve, reject) => { this.resolveCallback = resolve; this.rejectCallback = reject; if (this.callbacks.length === 0) { setTimeout(() => this.aborted ? reject([]) : resolve([]), 0); } }); let min = this.maxConcurrentPromises; let length = this.callbacks.length; if (min > length) { min = length; } for (let i = 0; i < min; ++i) { this.startNext(); } return this.endPromise; } public abort() { this.aborted = true; this.rejectCallback(this.callbackResults); } private startNext() { if (this.aborted) { return; } ++this.startedPromises; let index = this.nextIndex; let callback = this.callbacks[this.nextIndex++]; let promise = callback(); promise.then(result => { if (this.aborted) { return; } this.callbackResults[index] = result; ++this.resolvedPromises; if (this.resolvedPromises === this.callbacks.length) { this.resolveCallback(this.callbackResults); } else if (this.startedPromises < this.callbacks.length) { this.startNext(); } }); } public getEndPromise(): Promise { return this.endPromise; } }