- Unify test data for nonexistence of an object/string and collection. - Introduce more test through adding missing test data to existing tests. - Improve logic for checking absence of values to match tests. - Add missing tests for absent value validation. - Update documentation to include shared test functionality.
63 lines
1.9 KiB
TypeScript
63 lines
1.9 KiB
TypeScript
export type CallbackType = (..._: unknown[]) => void;
|
|
|
|
export function throttle(
|
|
callback: CallbackType,
|
|
waitInMs: number,
|
|
timer: ITimer = NodeTimer,
|
|
): CallbackType {
|
|
const throttler = new Throttler(timer, waitInMs, callback);
|
|
return (...args: unknown[]) => throttler.invoke(...args);
|
|
}
|
|
|
|
// Allows aligning with both NodeJs (NodeJs.Timeout) and Window type (number)
|
|
export type TimeoutType = ReturnType<typeof setTimeout>;
|
|
|
|
export interface ITimer {
|
|
setTimeout: (callback: () => void, ms: number) => TimeoutType;
|
|
clearTimeout: (timeoutId: TimeoutType) => void;
|
|
dateNow(): number;
|
|
}
|
|
|
|
const NodeTimer: ITimer = {
|
|
setTimeout: (callback, ms) => setTimeout(callback, ms),
|
|
clearTimeout: (timeoutId) => clearTimeout(timeoutId),
|
|
dateNow: () => Date.now(),
|
|
};
|
|
|
|
interface IThrottler {
|
|
invoke: CallbackType;
|
|
}
|
|
|
|
class Throttler implements IThrottler {
|
|
private queuedExecutionId: TimeoutType;
|
|
|
|
private previouslyRun: number;
|
|
|
|
constructor(
|
|
private readonly timer: ITimer,
|
|
private readonly waitInMs: number,
|
|
private readonly callback: CallbackType,
|
|
) {
|
|
if (!timer) { throw new Error('missing timer'); }
|
|
if (!waitInMs) { throw new Error('missing delay'); }
|
|
if (waitInMs < 0) { throw new Error('negative delay'); }
|
|
if (!callback) { throw new Error('missing callback'); }
|
|
}
|
|
|
|
public invoke(...args: unknown[]): void {
|
|
const now = this.timer.dateNow();
|
|
if (this.queuedExecutionId !== undefined) {
|
|
this.timer.clearTimeout(this.queuedExecutionId);
|
|
this.queuedExecutionId = undefined;
|
|
}
|
|
if (!this.previouslyRun || (now - this.previouslyRun >= this.waitInMs)) {
|
|
this.callback(...args);
|
|
this.previouslyRun = now;
|
|
} else {
|
|
const nextCall = () => this.invoke(...args);
|
|
const nextCallDelayInMs = this.waitInMs - (now - this.previouslyRun);
|
|
this.queuedExecutionId = this.timer.setTimeout(nextCall, nextCallDelayInMs);
|
|
}
|
|
}
|
|
}
|