There comes a time when several events fire in a row, and we don’t want to do something on every event, but rather, when the events stop firing for a specified period. In the typeahead case, we only want to make requests when the user stops typing. A function set up in this way is known as a debounced function. To create such a debounced function, you pass a function into debounce, which then returns another function that wraps the original function:
| let logPause = () => console.log('There was a pause in the typing'); |
| // This won't work, it will log on every keystroke |
| // input.addEventListener('keydown', logPause); |
| // Instead, we debounce logPause |
| let logPauseDebounced = debounce(logPause); |
| input.addEventListener('keydown', logPauseDebounced); |
You can even write your own helper to wrap a regular function into a debounced function:
| function debounce(fn, delay=333) { |
| let time; |
| return function (...args) { |
| if (time) { |
| clearTimeout(time); |
| } |
| time = setTimeout(() => fn(...args), delay); |
| } |
| } |
Choosing a duration to wait in a debounce is more of an art than a science. A default of 333 ms usually works when waiting for a user to stop typing. |
Debounce can be a bit confusing at first. Let’s put our debounce function through an example and watch what goes on:
| let f = debounce((num) => console.log('debounced! Arg:', num)); |
| |
| // Call synchronously |
| f(1); |
| f(2); |
| f(3); |
| f(4); |
| f(5); |
| |
| // Call several times in a short interval |
| let i = 0; |
| let interval = setInterval(() => { |
| console.log(++i); |
| f(i); |
| }, 100); |
| |
| setTimeout(() => clearInterval(interval), 1000); |
Sometimes a debounce is more complicated than what you really need. The throttle operator acts as a time-based filter. After it allows a value through, it won’t allow a new value, until a preset amount of time has passed. All other values are thrown away. This can be useful when you connect to a noisy websocket that sends a lot more data than you need. For instance, you might be building a dashboard to keep the ops folks informed about all of their systems, and the monitoring backend sends updates on CPU usage several dozen times a second. DOM updates are slow, and that level of granularity isn’t helpful anyway. Here, we just update the page every half-second.
| cpuStatusWebsocket$ |
| .pipe(throttle(500)) |
| .subscribe(cpuVal => { |
| cpuPercentElement.innerText = cpuVal; |
| }); |
debounce wouldn’t work in this scenario; it would be left eternally waiting for a time when there is a pause in the updates around CPU usage. In the typeahead case, we do want to wait for a pause in activity, so we’ll use debounce instead of throttle.
18.191.147.77