In this article, we're going to talk about two of the most important functions in frontend development, throttle and debounce.
Both of these functions are commonly used to deal with situations where a user is causing an event handler to fire on repeat and we want to slow down the underlying function call. Think scroll handlers, keyboard events, or even just clicking a button rapidly.
Throttle is a higher order function that takes a function and a timeout and only allows that function at most once per the amount of time specified.
Before we use throttle. Let's start with an example.
In this example above we have a piece of state
numSpells and a function that can increment this state
castSpell. We have a button we can click that increments
numSpells. You can play with it below.
Now you can click this button as quickly as you want and it will increment the number of spells cast. Now spells take some time to recharge, so we can use throttle to slow down the user.
We now have a button that call
castSpellThrottled when the user clicks it. Try it out below.
Now that we understand what it does, lets dive into the source of throttle.
What's going on here? Throttle is a function that takes a function and a timeout. Throttle returns a new function that forms a closure around the original one.
We keep track of a
ready that stops the inner function from firing unless the timeout has elapsed. If we fire the function we also fire a timeout that will set
ready to true when the timeout has elapsed. Pretty cool, huh?
Debounce is a bit different from throttle. With throttle we slow down function calls as they happen, with debounce we don't fire at all until the user has stopped calling it.
This is really useful if you want to perform a computation or hit an api when the user is done typing or done scrolling. Let's play with another example.
You'll notice, you can click Cast Spell Debounced as many times as you'd like, but it will only fire after you've stopped clicking it. The source for this is similar to the previous component.
Now that we know what it does, let's see how it works.
While the last one relied on a simple boolean, with this one we need to maintain access to the timer. We do this because we need to clear the previous timeout every time the function gets called again. We then set a new timeout to call the inner function.
Now these implementations of throttle and debounce were both very naive. If you take a look at the lodash source you can see a much more advanced version of debounce that considers things like calling the function before or after we set the timeout.
It also allows fancy things like a
maxWait and a
cancel. They even implement throttle with debounce.