2019-02-02 16:17:44 +01:00
|
|
|
/**
|
|
|
|
* Use as a Decorator on async functions, it will limit how many times the function can be
|
|
|
|
* in-flight at a time.
|
|
|
|
*
|
|
|
|
* Calls beyond the limit will be queued, and run when one of the active calls finishes
|
|
|
|
*/
|
|
|
|
export function throttle(limit: number, throttleKey: (args: any[]) => string) {
|
|
|
|
return <T>(target: any, propertyKey: string | symbol,
|
2019-02-02 18:26:46 +01:00
|
|
|
descriptor: TypedPropertyDescriptor<(...args: any[]) => Promise<T>>) => {
|
2019-02-02 16:17:44 +01:00
|
|
|
const originalMethod: () => Promise<T> = descriptor.value;
|
2020-08-12 21:42:42 +02:00
|
|
|
const allThrottles = new Map<any, Map<string, (() => void)[]>>();
|
2019-02-02 16:17:44 +01:00
|
|
|
|
|
|
|
const getThrottles = (obj: any) => {
|
|
|
|
let throttles = allThrottles.get(obj);
|
|
|
|
if (throttles != null) {
|
|
|
|
return throttles;
|
|
|
|
}
|
2020-08-12 21:42:42 +02:00
|
|
|
throttles = new Map<string, (() => void)[]>();
|
2019-02-02 16:17:44 +01:00
|
|
|
allThrottles.set(obj, throttles);
|
|
|
|
return throttles;
|
|
|
|
};
|
|
|
|
|
|
|
|
return {
|
2019-02-07 21:43:11 +01:00
|
|
|
value: function(...args: any[]) {
|
2019-02-02 16:17:44 +01:00
|
|
|
const throttles = getThrottles(this);
|
|
|
|
const argsThrottleKey = throttleKey(args);
|
|
|
|
let queue = throttles.get(argsThrottleKey);
|
2019-02-02 18:08:53 +01:00
|
|
|
if (queue == null) {
|
2019-02-02 16:17:44 +01:00
|
|
|
queue = [];
|
|
|
|
throttles.set(argsThrottleKey, queue);
|
|
|
|
}
|
|
|
|
|
|
|
|
return new Promise<T>((resolve, reject) => {
|
|
|
|
const exec = () => {
|
2019-03-05 23:12:51 +01:00
|
|
|
const onFinally = () => {
|
2019-02-02 18:08:53 +01:00
|
|
|
queue.splice(queue.indexOf(exec), 1);
|
|
|
|
if (queue.length >= limit) {
|
|
|
|
queue[limit - 1]();
|
|
|
|
} else if (queue.length === 0) {
|
|
|
|
throttles.delete(argsThrottleKey);
|
|
|
|
if (throttles.size === 0) {
|
|
|
|
allThrottles.delete(this);
|
2019-02-02 16:17:44 +01:00
|
|
|
}
|
2019-02-02 18:08:53 +01:00
|
|
|
}
|
2019-03-05 23:12:51 +01:00
|
|
|
};
|
|
|
|
originalMethod.apply(this, args).then((val: any) => {
|
|
|
|
onFinally();
|
|
|
|
return val;
|
|
|
|
}).catch((err: any) => {
|
|
|
|
onFinally();
|
|
|
|
throw err;
|
2019-02-02 18:08:53 +01:00
|
|
|
}).then(resolve, reject);
|
2019-02-02 16:17:44 +01:00
|
|
|
};
|
|
|
|
queue.push(exec);
|
|
|
|
if (queue.length <= limit) {
|
|
|
|
exec();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
};
|
|
|
|
};
|
|
|
|
}
|