Implementing Debounce in Javascript

I am generally pretty comfortable with `this`-binding in javascript, but today I managed to confuse myself for a few minutes. I was implementing a simple debounce function for fun and found myself lost in a web of `this`.

Debouncing makes it so a function can only be executed after a certain amount of time since it was last invoked. For example, “only execute this function if it has been 1000 milliseconds since it was last invoked.”

A common use case for debouncing is in search bars. We don’t want to make expensive http requests while the user is still typing their query, so we debounce the requesting function and only invoke it once the user has stopped typing.

Here was my first attempt at implementing debounce.

At first glance, this seems to work as expected.

After taking a look at underscore’s implementation of the same function, I realized that I had made a critical oversight. Namely, what happens when a debounced function is used as a method on an object?

That’s not right! Let’s figure out what’s going on here.

When I invoke `func` inside setTimeout, it is a free function invocation. Free function invocations in javascript have `this` bound to the global scope. However, we want `this` to be bound to the context in which it was invoked (ie. the object amy).

My first attempt to solve this bug was to get the context when the debounced function is invoked and bind that to the original function via apply.

`amy.speak` still yields ‘My name is undefined’. The problem now is the lexical-`this` binding of arrow functions. With normal javascript functions, `this` is bound when the function is called. With arrow functions, `this` is bound to the context in which the function is originally created.

This is an awesome feature, usually. In conforms more to our intuition about how `this` should behave. But we cannot forget about `this` just because we have arrow functions, and this is a perfect example of why.

`this` is still bound to the global scope in my updated function.

debounce(sayHello)

When I call `debounce` on `sayHello`, the creation context is the global scope, since debounce is a free-function invokation. Therefore, the arrow function returned from debounce is also bound to the global scope.

We specifically want `this` to be set at call-time, so an arrow function is the wrong tool entirely. By switching from an arrow function to a traditional anonymous function, we obtain a fully working solution.

The moral of the story is that arrow functions don’t absolve you from thinking hard about `this`-binding. In fact, it is more important than ever to have a strong grasp of how `this` behaves in javascript. If I had done the following, no fancy work inside my debounce function could have saved me. `sayHello` would be inextricably bound to the global scope.

const sayHello = () => console.log('My name is', this.name)

Keep this advice this in mind when using 3rd-party libraries as well. D3 and Mocha come to mind as libraries that can behave unexpectedly if you pass arrow functions as callbacks.

Arrow functions are great, but you need to know how they work, when to use them, and when to avoid them.

Like what you read? Give Griffin Michl a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.