Better event throttle
Tags: javascript, programming, tutorial
Published: 2024-02-26 11:57:47

**Sometime you need to make your code slower to make it faster...**

Usually we want our code execute as fast as possible. But in case of DOM events, it can be useful to limit execution rate (see this [post](https://hackernoon.com/optimizing-performance-with-throttling-in-javascript)). Some events like window scroll may fire with a very high frequency, which may lead to a poor user experience or even blocking of the event chain.

You will find numerous implementations of throttle functions, the most common is presented [here](https://blog.webdevsimplified.com/2022-03/debounce-vs-throttle/). Usually throttle executes the first event and then limits the rate of repetition. But in a UI it may happen, that there is not a single event causing a state change, but a burst of events happening in a short time. You might have some coupled states that depend on each other. Firing the first event would propagate only the first state change, but the depending changes would be suppressed until the next update. To prevent this, you would use a "debounce" function, that waits until the state has settled before propagating the first event. 

As the situation happens frequently, I found it helpful to combine both functions in an advanced throttle function:

```JS
function throttle(cb, delay = 10, rate = 50) {
  let wait = false
  let args, timeout
  const tf = () => {
    if (args == null) {
      wait = false
      clearTimeout(timeout)
    } else {
      cb(...args)
      args = null
      timeout = setTimeout(tf, rate)
    }
  }

  return (...arg) => {
    if (wait) {
      args = arg
      return
    }
    wait = true
    setTimeout(() => {
      cb(...arg)
      timeout = setTimeout(tf, rate)
    }, delay)

  }
}
```
This version uses an initial delay before calling the event function. After that, it limits the throttle rate to a different value. After the last event has happened, the execution stops and the next event will be propagated after the initial delay again. The default values of 10 ms and 50 ms have been found the be nearly unnoticeable, but any other value may serve better in your case.

The throttle function can be used as a function wrapper, arguments provided to the result will be forwarded to the wrapped function (see example below)

```JS
  function showScroll(){
    out.textContent = "scroll-position = "+window.scrollY
  }
  window.onscroll =  throttle(showScroll,10,250)
  
  // function wrapper example
  function log(s){
     console.log(s)
  }
  const tlog = throttle(log,500,2000)
  tlog("This is a test")
```
You can try out a working example [here](https://flems.io/#0=N4IgtglgJlA2CmIBcAWAnAOgBwoDQgGcBjAJwHtZZkBtABlwEYGBmWjNWjgVgHYA2BjwZYeXALr4AZhAQEaoAHYBDMIiQgMACwAuYKviJkF2+MeQgAPJvhKoAPgA6CgATOLq7UudFNSkgXhtAF4HEABXbUkAWixQxxc3bQhtBDsTAm0LAHoklPh413dAr2VVEJAANwh4AHcABzISbVDvIxNjcprobU0gqHgqongorqge3GcIBWSIJVgo4jn4IIYMWjinQuISCDrtZwISInKdbTqCJCysoigFDAArAn7YCAqSDAVArIBzTSz4SR1JTfeBZAAiAFkADJZF4AI3B0KikDujzi2W2u20BTcmL2ONchgUGWccPg3ymziCB3gCCI2gAQkoAhNTFAqc4wsTafB6UyWaSoK8OU9Xps3Fk8dinNlrLZ4iB8AE6UkjHJ1FwkAAmKI8JBMEAAX1wihUag0j0VICJ7W05iyACpnFEXa63e6PZ6vd63eLXD1yNo8t45rA4UoiABrKKSLn0iBGbxwgAUAEo-c6fVnszmXc4HVknLGFPHEwGyEGEMmiHCJs8lABPDkMejOEhKEwcri0VPOYDihD7GpKZIcyRzAIDwLOPzfAgTJKqMgRcVEkmRDlpql2PsZiCSZzJ2cEKnUhRhSi9-sJVyuYej6nj2CTm+3ogIPwAFQgS4iycX8DLto6avoazi0gEu6voSKYYHBx4gbet7Hhy56UBm-o-oBEQioE36-to-6SBM7YmIht6GuKlEKOKJCBGEJAuMmcEYLOvZBDu15Ifuh73sBUFIa4KHUrOGFtvRjEZtRSF8Ry2gkGE8AZgE2j4dhhFbhxAmCTWzHwSQ3zkUhAFAbhqlYUBREkR28BGc4xrOPWDYgVRTjUU4HkKI6p5BL5fn+QFgVBb5GZwmEMjsj08DOAAqgAkhmwVJclAX5oWChwkKFTJqEoQTKEDQEDMRhIM40gAB7wFAADczgtAA1M4AD6cJkOVziNS1EDfAACrYQoKIZTimdSdRpk4bLjTRXlZN4dE2c4UXOGCADyEJFo0h6DpMza1RAbjOC2tB7fV9W9mNoSfpoEAnjdzgvJ8dUgB1kwudN3m5p9X2+q+UJkN8FKDWVcaqsSGbfRDuZpUWIMJi4BCaGQNRxQoMUQMmBBXs4VwHIjNSTC48XikBGAmOV2gAMJtKY+zUgQbmecWpYuLA-0Y1jOOs98i1kK0xIUEpCRrgLGBc+zDPTWu+wEKzNQAMp43JmiBnkGN4yjaMTC2WvdiBUsHLLXNKyrVZc1rtCtlqFs9p5XQKFASMYGqpAULAm7sZx4oy0jCtIzlhAu5QUSFcVLjUg1zh2w7NQYNsrsAJrkd7NRi6EcuB27Ieg099VR47ceUInEtWsqvKg+qIB8FqSC0EaJogKU5oYEQBByAY1NmOoSElzyzMV7QqBGmIhpAA)