We literally had this exact same requirement.
Check out this blog post.
Here's the code:
class SmartDebounce {
private isBatching: boolean = false;
private debounceTimer: NodeJS.Timeout | null = null;
private readonly debounceInterval: number;
public hasQueuedUpdate: boolean = false;
constructor(debounceIntervalMs: number = 25) {
this.debounceInterval = debounceIntervalMs;
this.cleanup = this.cleanup.bind(this);
this.run = this.run.bind(this);
this.debounce = this.debounce.bind(this);
this.cleanup = this.cleanup.bind(this);
}
public run(callback: () => Promise < void > | void): void {
if (!this.isBatching) {
this.isBatching = true;
this.debounce(() => {
this.isBatching = false;
});
// Run the callback immediately
this.hasQueuedUpdate = false;
callback();
} else {
this.hasQueuedUpdate = true;
this.debounce(() => {
this.isBatching = false;
this.hasQueuedUpdate = false;
callback();
});
}
}
private debounce(callback: () => Promise < void > | void): void {
if (this.debounceTimer) {
clearTimeout(this.debounceTimer);
}
this.debounceTimer = setTimeout(() => {
callback();
}, this.debounceInterval);
}
public cleanup(): void {
if (this.debounceTimer) {
clearTimeout(this.debounceTimer);
}
}
}