Skip to content

Instantly share code, notes, and snippets.

@rigwild
Last active September 26, 2021 12:50
Show Gist options
  • Save rigwild/02c28eb3e73075804cb1002a808dc343 to your computer and use it in GitHub Desktop.
Save rigwild/02c28eb3e73075804cb1002a808dc343 to your computer and use it in GitHub Desktop.
Debounced task queue, will skip function calls if serialized arguments were already seen recently
export type Task<F extends (...args: any) => any> = { thisArg: any; fn: F; args: Parameters<F> }
/**
* Debounced task queue, will skip function calls if serialized arguments were already seen recently
*
* @author rigwild <[email protected]> (https://github.com/rigwild)
* @see https://gist.github.com/rigwild/02c28eb3e73075804cb1002a808dc343
*/
export class DebouncedTasksQueue {
private debounceMs: number = 10000
private intervalId: ReturnType<typeof setInterval> | undefined
private lastSeenTaskCallArgs = new Map<string, number>()
tasksQueue: Task<any>[] = []
constructor() {}
poll(): Promise<any> {
// Cleanup map
for (const [k, v] of this.lastSeenTaskCallArgs.entries()) {
if (v < Date.now() - this.debounceMs) this.lastSeenTaskCallArgs.delete(k)
}
const polled = this.tasksQueue.shift()
if (polled) {
// console.log('polled', polled, this.lastSeenTaskCallArgs)
const { thisArg, fn, args } = polled
const serializedArgs = args.map(x => JSON.stringify(x)).join()
if (!this.lastSeenTaskCallArgs.has(serializedArgs)) {
this.lastSeenTaskCallArgs.set(serializedArgs, Date.now())
return Promise.resolve(fn.apply(thisArg, args)).catch(() => {})
} else {
// Was debounced, call the next one immediately
return this.poll()
}
} else return Promise.resolve(undefined)
}
push<F extends (...args: any) => any>(thisArg: any, fn: F, ...args: Parameters<F>): void {
this.tasksQueue.push({ thisArg, fn, args })
}
clear() {
this.tasksQueue = []
}
start({
logResult = false,
debounceMs = this.debounceMs,
nextCallIntervalMs = 500,
callImmediately = true
}): Promise<any> | undefined {
if (debounceMs) this.debounceMs = debounceMs
const fn = logResult
? async () => {
const res = await this.poll()
if (res !== undefined) console.log(res)
return res
}
: this.poll
this.intervalId = setInterval(fn, nextCallIntervalMs)
if (callImmediately) return fn()
}
stop() {
clearInterval(this.intervalId as Parameters<typeof clearInterval>[0])
}
}
// Usage example
const debouncedQueue = new DebouncedTasksQueue()
debouncedQueue.start({ logResult: true, debounceMs: 30_000, nextCallIntervalMs: 1000 })
var i = 0
setInterval(() => {
i += 100
debouncedQueue.push(this, Math.min, 500, i)
if (i > 1500) i = 0
}, 100)
// Or just poll manually, will skip debounced tasks automatically and return the proper result
console.log(await debouncedQueue.poll())
console.log(await debouncedQueue.poll())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment