Open FND opened 4 months ago
I think this is exactly one of those cases I've found over the last 10-15 years that always has to be implemented somewhere in a UI, but requires a library or additional code to do.
+1 for non-breaking additional properties that would add this support at the platform level for denouncing.
Throttle and denounce are not the same IMHO so both could be valid. Denounce is the event handler on last event, but you could still slow down a UI without throttling.
I find that this is most relevant when the handler itself will take time to execute, a variable amount of time, and there would be negative consequences of simultaneous execution of several instances of that asynchronous handler, e.g. an autocomplete input not ultimately displaying the final set of suggestions due to a race condition.
As such, I think that the usefulness of this feature would be greatly increased if it detected promises returned by the handler and ensured that they reached resolution before allowing another invocation.
@boutell I feel like await: true
would be a better/explicit guard to that and it can be used to already queue same async listener multiple times ... it's indeed a common footgun not always understood by developers that multiple dispatches to an async listener doesn't mean that the previous same listener completed whatever it was doing and it easily cause broken states on the UI if the latest dispatch finished before the previous one.
edit this might have undesired side-effects around the event.currentTarget
and other properties gone by the time the latest async listener runs ... but that's why await: true
looks like an awesome guard beside debounce and throttle to solve even more common use cases.
See also webwewant.fyi/wants/4/ - judges pick and we did try to move that at some point iirc - I can't remember why it stalled
I don't have any strong objection to an explicit option to turn on the behavior of always settling the previous promise before starting another invocation, although that this is a new API to start with so it could be reasonable to define its relationship to promises right from the start. If there's a super common case where this would be undesirable behavior I'd be interested to hear more about it.
I think I'd prefer throttle
and debounce
only accept a number. If they're not present, then they're off.
Either that, OR, if there needs to be a default duration, then:
Rather than allowing throttle
and debounce
to accept both booleans and numbers, I'd rather see separate fields for the booleans and numbers.
Something like:
{
debounce: true,
debounceDuration: 200,
throttle: true,
throttleDuration: 200,
}
And the *Duration
fields would be optional. The duration would fall back to the default duration if those were not specified.
Would it be better to make it so that you can't set both? Something like...
somethingBikesheddable: { type: 'throttle', duration: 200 }
Where I am not even proposing a name for this, but just a way to structure the arguments such that you don't both debounce and throttle?
I like the thinking here, though we might also want to consider whether there's a precedent or whether we'd be introducing new API patterns (which might be warranted, but should be a conscious decision). Right now, I'm struggling to think of anything comparable within existing APIs, though that doesn't necessarily mean much...
I'd prefer if these functions (throttle/debounce) were built-in to javascript. Limiting the eventListener call rate is useful but it's also limiting.
Let's say for example i have two actions to perform when an event is triggered, one of them requires throttling and the other doesn't, that will force me to create 2 event listeners.
+1 for built-in throttle and debounce functions independently from this proposal.
It may be worth proposing these extensions as part of the Observables proposal which has the ability to add prototype methods, has a contract of variable timing (sync or async - therefore has good precedence for such operations), and is not strictly coupled to Events.
@keithamus et al.: I don't disagree, but the original proposal quite intentionally limited its scope to the event context - in part because that seemed like a potential quick win[^1], but also for ergonomic reasons: Even if debouncing and throttling were available in some other form (via observables[^2], as stand-alone functions etc.), exposing such functionality via corresponding addEventListener
options would still add considerable value IMHO. (Note that OP's rationale also included broader aspects like educational benefits and potential context-specific optimizations.)
[^1]: Unfortunately, the enthusiasm here and elsewhere has yet to translate into interest by implementers though. Despite various efforts, it remains unclear how to get feedback from browser vendors.
[^2]: FWIW, both debouncing and throttling had already been mentioned in WICG/observable#35.
What problem are you trying to solve?
Developers often need to limit the frequency with which event handlers are being executed, both to improve performance and for behavioral reasons. Typically there are two distinct purposes, particularly when dealing with browser events fired in rapid succession: Debouncing delays the respective action until a steady state has been reached while throttling limits execution to once per time frame. (For details, see Debouncing and Throttling Explained Through Examples, perhaps also consult another visualization.)
Examples include avoiding excessive GUI updates, either for performance reasons (e.g. in response to resizing or scrolling) or to prevent flickering (e.g. when visualizing pointer coordinates or network-connection states), as well as reducing the frequency of network requests (e.g. auto-completion for keyboard inputs). A proposal from 2017 by @simevidas discusses additional use cases and considerations.
Providing a standardized approach for this common operation would enhance the web platform by reducing the need for custom implementations or third-party dependencies (in fact, it's not uncommon for an application to include multiple such implementations). In addition to browsers offering a reliable and efficient implementation, adding this capability to the platform would likely provide educational benefits by raising general awareness of debouncing and throttling.
What solutions exist today?
There are myriad JavaScript implementations for both debouncing and throttling, going back to at least 2009 with John Hann and Ben Alman. The concepts are also widely used in reactive programming; there might be parallel efforts within the context of the recent JavaScript proposals for observables and signals.
Presumably browser internals already include this functionality, without it being exposed to web developers.
How would you solve it?
Here we're primarily trying to address the issue of controlling browser events fired in rapid succession. Thus the obvious solution seems extending
addEventListener
with two new options:debounce
andthrottle
(complementingonce
).Note that a general-purpose solution for debouncing and throttling in JavaScript would exceed the scope of this particular proposal. (Though it's conceivable that might happen naturally in the future, if perhaps more through convention than via shared implementations.)
That leaves the question which values those options should assume:
debounce: true
andthrottle: true
would leave it up to browsers to decide a suitable delay, which might have performance and other contextual benefits (cf.requestAnimationFrame
). While such a default will often be just fine, sometimes developers will need more control.debounce: 200
andthrottle: 200
could specify a delay in milliseconds, much like withsetTimeout
, most likely addressing 90+ % of all use cases.My inclination is that supporting both boolean and numbers would be nice, but I'm not sure there's a precedent for that. It might also complicate documentation and education.
Anything else?
While I had considered creating a speculative polyfill, a future-proof implementation requires feature detection - that seems tricky for
addEventListener
options? In fact, support detection might need additional consideration before any such feature is introduced.I've belatedly realized #1070 already exists (thanks to domenic's issue transfer); is there a process for merging both issues?