Comment by achou
6 months ago
One thing to watch out for when using debounce/throttle is the poor interaction with async functions. Debounced/throttled async functions can easily lead to unexpected behavior because they typically return the last result they have when the function is called, which would be a previous Promise for an async function. You can get a result that appears to violate causality, because the result of the promise returned by the debounce/throttle will (in a typical implementation) be from a prior invocation that happened before your debounce/throttle call.
There are async-safe variants but the typical lodash-style implementations are not. If you want the semantics of "return a promise when the function is actually invoked and resolve it when the underlying async function resolves", you'll have to carefully vet if the implementation actually does that.
Another thing to watch for is whether you actually need debouncing.
For example, debouncing is often recommended for handlers of the resize event, but, in most cases, it is not needed for handlers of observations coming from ResizeObserver.
I think this is the case for other modern APIs as well. I know that, for example, you don’t need debouncing for the relatively new scrollend event (it does the debouncing on its own).
Sad that the `scrollend` event isn't supported by Safari and doesn't look to be a part of their release this fall either.
Yep. Apple is a weird organization. scrollend is in Chrome since May 2023 and in Firefox since January 2023.
Reactive programming (such as with RxJS [0]) can be a good solution to this, since they provide primitives that understand time-based dependencies.
[0]: https://rxjs.dev/api/index/function/switchMap
Debouncing correctly is still super hard, even with rxjs.
There are always countless edge cases that behave incorrectly - it might not be important and can be ignored, but while the general idea of debouncing sounds easy - and adding it to an rxjs observable is indeed straightforward...
Actually getting the desired behavior done via rxjs gets complicated super fast if you're required to be correct/spec compliant
can you recommend some reading about the topic? also maybe point to some specs that require (and describe?) fully correct debouncing? thanks!
1 reply →
That doesn't sound correct. An async function ought to return a _new_ Promise on each invocation, and each of those returned Promises are independent. Are you conflating memoization? Memoized functions will have these problems with denouncing, but not your standard async function.
If I understand it correctly, they're saying the debounce function itself usually implements memoization in a way that will return you stale promises.
this sounds interesting but it's a bit too early here for me. by any chance can we (not simply a royal we :D) ask you to provide a code example (of a correct implementation), or a link to one? many thanks!
I would probably look at the lodash implementation (for JS/TS) as a complete implementation example.
https://github.com/lodash/lodash/blob/8a26eb42adb303f4adc7ef...
Damn, there was another thread not too long ago claiming that a sync does not mean concurrent - this would have been a great example to bring up.
So - events / handlers may need to be tagged as "for human interaction"? (to avoid over-filtering signals?)
Such stuff has first-class support in Kotlin: Structured concurrency simplifies multi-threaded programming pretty effectively.