Last week I presented a lightning talk at the jQuery Boston Meetup, Using Throttle and Debounce for Complex Interactions. I’ve posted the slides, but here’s a quick summary of the talk part of the talk:
Many of the jQuery UI widgets provide event hooks that notify you of interaction events (like the user moving a slider) and allow you to update the DOM to reflect the change. This is a commonly used and powerful way to build interactions.
But what happens when the update action takes a long time? The update is synchronous with the user’s interaction with the slider, creating a terrible experience. [The Dude disapproves]
One thing you might try is to schedule the update using
That didn’t work at all because there’s really only one thread for a user’s interaction in a browser window. If your update takes a half second to run, it doesn’t matter if you do it inline in the event callback or schedule it with
setTimeout–it’s going to compete with the user’s interaction with the slider and make for an unpleasant experience.
Instead, you might want to throttle your response to the events–update once per second or so, no matter how quickly the callbacks arrive. In my example I am using
throttle from Underscore.js library, which I highly recommend. We pass our event handler to
_.throttle, which returns a wrapper function which will only execute your event handler once per 100ms.
As you can see in the example, that works a bit better, but it still doesn’t feel quite right. The pauses for the updates seem to happen at random times relative to the user’s actions.
debounce wrapper is like
throttle, but instead of running the event once per specified interval, it waits for the input events to “calm down”. Specifically, it waits until no new input events have arrived for the specified interval, then calls the debounced function. In my example I used 200ms. [Yup].
Since this is a jQuery meetup, I wondered how you would implement this using pure jQuery, even though Underscore’s
debounce are highly recommended. So here’s debounce written in jQuery using jQuery
Deferred. Essentially we return a function which creates a new Deferred which will wait the specified time and then call the specified
complete method. While waiting for that timeout, every time a new input event arrives, we cancel the Deferred by calling its
reject method and start a new one. Eventually, one of these will expire before a new event arrives to cancel it, and the completion callback is called.
Why not use the
debounce works very well for a slider, where you may get many
slide events while the user moves the handle toward the desired value, and then one update when he or she pauses. Why not use the
change event, then? It will only fire when the user releases the handle at the destination, resulting in a single event that needs update. I think this sort of defeats the purpose of responding to the
slide events, though. Essentially you’re not providing any of the feedback that made this interactive in the first place.
Origins of Debounce
I first came across the term debounce while implementing network management systems, where ‘debounce’ is used alongside ‘damping’ and ‘hold down timer’ to control the way certain events are propagated through the system. One common case is handling interfaces which are rapidly “flapping” up and down. Here’s an example from the BGP and MPLS section of a Juniper manual:
Administrative debounce-time - whether the up/down state of the interface will be debounced or damped if a link periodically fails and immediately comes back; the time delay (configured in Interface Configuration mode) that an interface must remain in a new state before the routing protocols react to the state change
The term originated from engineers working with electromechanical switches (like relays in analog telephone switches) which would physically bounce when changing state:
Switch Debounce is a mechanical switch bounces or changes state between open and close many times when the switch is moved from one position or the other [contact bounce]. A switch de-bounce circuit inhibits those contact changes from reaching the circuitry reading the switch position. The debounce circuit needs to eliminate all of the mechanical oscillations so that the circuits receiving the signal only sees one circuit open or closer. Other wise the circuit would see dozens of switch changes even though the switch button was only moved once.