Hey ๐ fellow front-end developers,
Today I'll be sharing two important concepts for optimizing function calls(or API requests) in your app. These concepts are hot ๐ฅ interview questions too, so you should understand them really well.
Both of these concepts are used in different situations, albeit there's minor difference in the approach.
โ It's important that you understand closures and setTimeout
before going further.
There's a really good example in the end that will make sure you never forget these concepts. ๐
Debouncing
Let's say we have button on the screen and it has an event handler attached to it. When the button is clicked, it calls an API and gets some data.
But there's a problem with this code. If a user repeatedly clicks on the button in a short span of time, we execute the handler function each time. Hence, we call API multiple times and that is not optimal. What's a better solution?
By using debouncing, we can prevent unnecessary calls. Debounced function will run a timer and when this timer expires it will execute the function. But if the timer is still running and user clicks on the button again, the timer resets.
Let's say our timer waits for 1s. Even if the user clicks the multiple times, the function will only be executed 1s after the last click.
I have made a generic function that can debounce any function(fn
) you provide it. It also take an optional param delay
, if not provided it's value is 300ms.
How does this work?
First, we wrap our function with debounce
and pass time as 1000(1s). It simply means we want event handler to be executed 1s after event occurred.
debounce
function returns a function as it is needed by the addEventListener
function. The returned function forms a closure and has access to the timer
variable always.
When user clicks on the button, we check if there's an existing timer running and clear that timer
using clearTimeout
. After this we initiate a new timer of 1s and store it back in timer
.
If the user clicks again within next 1s, the timer resets again. Otherwise, after 1s fn
is executed.
Apart from the normal flow, I have also saved the context(
this
) forfn
and also passed arguments, if it received any. This is not required to understand debouncing.
If you want to understand function methods likecall()
,apply()
andbind()
. You can read this short article here.
โญ The most popular application of debouncing is search fields. For example, you have an e-commerce site where the user can search for products and you want to provide them suggestions as they type. Without debouncing, you'll be making API calls for every character they type as each keystroke is an event.
With debouncing, you can limit this to maybe 2 or 4 API calls(depending on the user's typing speed). Also, in the example, I have the delay
as 1s but in real projects it's way less.
You can try building your own search box with debouncing now. ๐
Throttling
If you've understood Debouncing, this will be fairly simple. This approach allows function execution after certain intervals.
For example, we've our same old button but this time it's implemented with a throttle
function with a delay
of 1s.
A user clicks on it repeatedly for some time.
- On the first click, provided
fn
is called. - For the next 1s all the clicks will be ignored.
- Any click after 1s will be accepted and
fn
will be called again. - Repeat steps 2 and 3.
When would you use this?
A good example is browser re-sizing or tracking user mouse events.
If you add debouncing to this, you would only get one function call once the user stops moving their mouse. But with throttling, you can get evenly spaced function calls even if the user keeps moving their mouse relentlessly.
Just like we did for debounce
, we wrap our function with throttle
and pass in delay
. This returns a function which can be used as event handler.
The trick is to store the time when the function was last executed. Next time when the function is called, we check if delay
time has passed or not. If yes, we execute the fn
and updated lastExecutedAt
for the next call.
There's an alternate implementation too using setInterval
but this would also suffice.
Real world analogy b/w the two
Let's say you're standing in front of an elevator. The elevator has a button next to it to open the doors. You're are repeatedly pressing the button. You're really drunk and had a fun night(before pandemic, of course). ๐
Assuming delay for the button as 3s in both the cases.
Case: Debouncing
You have been pressing the button for last 5 minutes, the elevator doesn't open. You give up and 3s later the elevator opens.
Case: Throttling
You have been pressing the button for last 5 minutes, the elevator opens at 3s intervals. First at 0s, then 3s, then 6s and so on.
๐ It's important to note that if you press the button at 4s and 5s and then stop, no call will be made. You have to press the button after 6s to make the call. In JavaScript terms, you have to generate an event after 6s for the function to execute. There's starvation in case of throttling.
๐ That's it for this one. I hope you got some idea about these concepts and will use them in your projects.
Top comments (2)
Why do we need to pass context to fn.call(context, ...args) ? I don't see any value that need to bind to the execution function
The throttled function can be used as a method object or as an event handler. To preserve the context, it's important to bind the actual
this
.