Gohomewho

Posted on

# Debounce is a good example of closure

I can't remember how many times I learn a new topic and left with confusion. If I come across a same topic, I'll try to learn it again. Often, it's not because we don't understand it. it's because we don't know the practical use case. We don't know how to apply it, therefore, we think we don't understand.

In programming, there are so many abstract concepts. Closure is an example that I've tried to learn so many time from different resources. Last time when I was learning it again, and I found that I've been using it without realizing it.

## What is closure?

This is a definition of closure from w3schools.

A closure is a function having access to the parent scope, even after the parent function has closed.

Let's forget about closure for a moment.

Here we define a function called `counter` and define a variable `num`. We then define another function inside `counter` called `add`. Inside `add`, we can access the `num` defined in the parent scope. Next, we call `add()`.

``````function counter(){
let num = 0
num++
console.log('num is: ', num)
}
}
``````

We can open the devtools and run the code in console to quickly examine if something works. Let's call `counter` function a few times.

``````counter()
// num is:  1
counter()
// num is:  1
counter()
// num is:  1
``````

We can confirm that `num` defined inside parent scope `conuter` can be accessed inside child scope `add` because `num` is properly logged in the console.

Let's modify the example above. This time we don't call `add()` directly inside `counter`. We return the function `add`.

``````function counter(){
let num = 0
num++
console.log('num is: ', num)
}
}
``````

We can use a variable to store what's returned from calling `counter()`.

``````const c = counter()
``````

We know that `counter` returns a function. So we know `c` is a function. Let's call `c()` a few times. We can see that `num` is increasing

``````c()
// num is:  1
c()
// num is:  2
c()
// num is:  3
``````

Previously, when we call `counter()` a few times, the results are the same `num is: 1`. That's because we always create a new `num`, add one to it, and log it.

When we call `counter()`, we can imagine that we create a new context, and the context is returned along with `add`. Here we only call `counter()` once. We store the function returned from `counter()` inside a variable `c`, so `c` is the function `add` inside `counter`. We knew that `add` have access to `num` and what it does.

After `counter()` is closed and `c` is returned, `c` still have access to the context that was created. That's why calling `c()` can increase `num`.

``````function counter(){
let num = 0
num++
console.log('num is: ', num)
}
}

const c = counter()

c()
// num is:  1
c()
// num is:  2
c()
// num is:  3
``````

Let's recap the definition from w3schools again.

A closure is a function having access to the parent scope, even after the parent function has closed.

When we define a function that returns a function, that's a closure!

If you can understand the output of this code, then you already understand closure.

``````const c1 = counter()
const c2 = counter()

// calling c1
c1() // num is:  1
c1() // num is:  2
c1() // num is:  3

// calling c2
c2() // num is:  1
``````

## How to use a closure

You've probably learned closure before. Learning from the example like above again doesn't seem to help. That's what I felt! Why do we need closure? It seems to make things more complicated. Like I mentioned in the beginning, we have this feeling often because we don't see practical use case. Let's try another example.

For example, we are building a search feature. We listen to the input event, so every time users enter or remove something, the event will be triggered.

``````const input = document.querySelector('input')
const query = e.target.value
// send request to fetch resources
// this is pseudo code
fetchResources(query)
})
``````

If someone is searching for 'apple', each input trigger a request.

``````// each input send a request
'a'
'ap'
'app'
'appll' // make a typo
'appl'  // remove a typo
'apple'
``````

As a result, we will send more requests than we need to. To solve this, we can use a strategy called debounce to reduce the amount of requests we send to server.
You can learn more about debounce from this arttcle by Josh Comeau

Let's add debounce to our search feature. we define a variable `timeout`. We wrap `fetchResources(query)` inside a `setTimeout`, so a request is not sent immediately when a event is triggered. We also store the action of `setTimeout` to `timeout`, so it can be canceled later. The delay is set to 200ms, so when another event is triggered between 200ms, `clearTimeout(timeout)` will cancel the previous action. Then `setTimeout` will create a new one.

``````const input = document.querySelector('input')
let timeout
const query = e.target.value
// send request to fetch resources
clearTimeout(timeout)
timeout = setTimeout(() => {
fetchResources(query)
}, 200)
})
``````

Now our code becomes clunky and we have a `let` variable `timeout` that can be modified anywhere. Luckily, we have a better way to do the same thing which is also safer and cleaner.

We extract the logic of debounce to a function. Its first parameter accepts a callback short as cb(a function). Its second parameter is how much time we want to delay the action. Calling `debounce()` will return a new function that accepts arbitrary arguments `...args`. Those arguments will be stored in an array. They will be passed to our action function `cb(...args)`.

``````function debounce(cb, delay = 1000) {
// the returned function have access to parent scope
// what is accessible here can be accessed inside the returned function
let timeout

// return a function - closure
return (...args) => { // ...args collect arguments as an array
clearTimeout(timeout)
timeout = setTimeout(() => {
cb(...args) // ...args spread the array elements as arguments
}, delay)
}
}
``````

You can learn more about `...` Rest parameters and Spread syntax on MDN.

We call `debounce(fetchResources,200)`create a debounced version of `fetchResources` with 200ms delay. `debouncedFetchResources` will be called when input event is triggered. But because it is a debounced version, it will only run after user stop triggering input event after 200ms, just like what we did above.

``````const input = document.querySelector('input')
// create a debounced version of fetchResources with 200ms delay
const debouncedFetchResources = debounce(fetchResources,200)
const query = e.target.value
// send request to fetch resources
debouncedFetchResources(query)
})
``````

If we want to debounce something else later, we can use that debounce function again. We don't need to define new variables like `timeout2` or `timeout3` to store other actions because an independent `timeout` variable is created when we called `debounce()` each time. We don't need to write extra `clearTimeout()` and `setTimeout()`. We also don't need to worry about accidentally modifying the `timeout` variables somewhere because they are hidden in the closure. Only the returned functions have access to their respective `timeout` variables.

If you just learn debounce and don't understand what it does, that's totally fine. The example here is try to make you understand what a closure can do, not a specific use case of closure.

## Wrap up

Sometimes we learn an abstract topic, we don't know how to apply it. Thus, we think we don't understand it. But it is not true. The idea lives in our head. We'll recognize it later when we encounter something. So Next time if you learn something new but have no idea where to apply it. Don't worry too much about it! We don't need to create a problem and solve it. We'll solve problems when we need to.