In my opinion, using decorators can greatly enhance the simplicity and reusability of this code:
/**
* Asynchronously pauses execution for a specified amount of time.
* @param milliseconds
* @returns
*/
function asyncPause(milliseconds: number): Promise<void> {
return new Promise<void>((resolve) => {
setTimeout(() => { resolve(); }, milliseconds);
});
}
/**
* Generates a random integer within a given range.
* The maximum is exclusive while the minimum is inclusive.
* @param min
* @param max
*/
export const getRandomInt = (
min: number,
max: number,
): number => (
Math.floor(
Math.random() * (
Math.floor(max) - Math.ceil(min)
) + Math.ceil(min),
)
);
/**
* Throttles the execution of a method by a fixed millisecond duration if a number is provided.
* If a tuple is passed, it will throttle by a randomly generated time (in ms) between each call.
* @param milliseconds
* @returns
*/
function throttle(milliseconds: number | [number, number]): any {
let lastCall = 0;
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = async function (...args: any[]) {
const currentTime = Date.now();
if (!lastCall) {
lastCall = currentTime;
return originalMethod.apply(this, args);
}
const duration = Array.isArray(milliseconds)
? getRandomInt(milliseconds[0], milliseconds[1])
: Math.round(milliseconds);
const timeDifference = currentTime - lastCall;
if (timeDifference < duration) {
await asyncPause(duration - timeDifference);
}
lastCall = Date.now();
return originalMethod.apply(this, args);
};
return descriptor;
};
}
Now you can simply implement it like so:
class MyClass {
@throttle(1000)
async limitedMethod() {
await performTask()
}
@throttle([1000, 5000])
async randomizedLimitedMethod() {
await performTask()
}
}
TSPlayground