Skip to content

Instantly share code, notes, and snippets.

@jsdw
Last active February 24, 2023 14:20
Show Gist options
  • Save jsdw/02ab0da412a96fa53a43a7b095158a58 to your computer and use it in GitHub Desktop.
Save jsdw/02ab0da412a96fa53a43a7b095158a58 to your computer and use it in GitHub Desktop.
A function to aggregate values, waiting at least waitMs times before sending off
function aggregateAndDebounce<A, B>(
// Take some val and the current aggregated vals in and return next aggregated vals
aggregate: (val: A, vals: B) => B,
// This is called waitMs after the some values are given
debounced: (val: B) => void,
// This produces an initial state for the aggregated vals, to start from/
initialVals: () => B,
// How long to wait until calling the debounce
waitMs: number
): (val: A) => void {
let intervalId: number | undefined = undefined
let vals = initialVals()
let newValsSeen = false
function callAggregate(val: A) {
vals = aggregate(val, vals)
newValsSeen = true
}
function callDebounced() {
if (newValsSeen) {
// New vals seen since last fire; send them out.
debounced(vals)
vals = initialVals()
newValsSeen = false
} else {
// No new vals seen in waitMs since last fire; stop interval
clearInterval(intervalId)
intervalId = undefined
}
}
return (val: A) => {
callAggregate(val)
if (!intervalId) {
callDebounced()
intervalId = setInterval(() => {
callDebounced()
}, waitMs)
}
}
}
// An example usage of the above with concrete types etc.
function debounceChannelUpdates(
func: (updates: Map<number, number>) => void,
waitMs: number
): (i: number, l: number) => void {
let fn = aggregateAndDebounce(
([a, b]: [number,number], map: Map<number, number>) => {
map.set(a, b);
return map;
},
func,
() => new Map(),
waitMs
);
return (a: number, b: number) => {
fn([a, b])
}
}
@cjcormack
Copy link

cjcormack commented Feb 24, 2023

Thanks James.

There is still an issue—if the timeout has just fired, then a call < waitMs later will fire immediately as timeoutId is undefined, whereas ideally we want it to debounce for waitMs - timeSinceLastTriggerMs.

Also, setTimeout should be window.setTimeout... I think!

@jsdw
Copy link
Author

jsdw commented Feb 24, 2023

Good point! I changed the timeout to an interval and added a thing so that if no new vals are seen since the last time it fired, the interval will finally stop.

setTimeout is fine; the window. bit is just assumed anyway (and anyway, in a web worker or whatever window. isn't available, so omitting it makes the thing able to be used in a web worker context too :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment