DEV Community

Cover image for QA: debouncing / throttling
Conan
Conan

Posted on

QA: debouncing / throttling

Photo by Jp Valery on Unsplash

This short Q&A on setTimeout() hopes to lead you into writing your own debouncers & throttlers -- handy tools for treating repeated events that happen across short periods of time as a single event.

Debouncing:

       Time : -   - --> -   - --> -   - --> -
Key-presses :        X X X X   X X X X X
     Events :              O           O
Enter fullscreen mode Exit fullscreen mode
Throttling:

       Time : -   - --> -   - --> -   - --> -
Key-presses :        X X X X X X X X X
     Events :        O     O     O
Enter fullscreen mode Exit fullscreen mode

All code below assumes the following header:

const seconds = n => n * 1000
const log = x => console.log(`hello, ${x}`)
Enter fullscreen mode Exit fullscreen mode

So, the question is:

"At the end of each run, what will be logged to the console?" 🤔

1. setTimeout / clearTimeout

let id

function run() {
  id = setTimeout(log, seconds(1), 'world')
}

function clear() {
  clearTimeout(id)
}

// 1a)
run()
// What will be logged?

// 1b)
run()
clear()
// What will be logged?
Enter fullscreen mode Exit fullscreen mode

2. runAfter

function runAfter(ms, fn, ...args) {
  const id = setTimeout(fn, ms, ...args)
  return () => clearTimeout(id) 
}

// 2)
const clear = runAfter(
  seconds(1),
  () => log('world')
)

clear()
// What will be logged?
Enter fullscreen mode Exit fullscreen mode

3. RunAfter

function RunAfter(ms, fn) {
  let id
  const clear = () => clearTimeout(id)
  const run = (...args) => {
    id = setTimeout(fn, ms, ...args)
  }
  return [run, clear]
}

const [run, clear] = RunAfter(
  seconds(1),
  () => log('world')
)

// 3)
run()
// What will be logged?
Enter fullscreen mode Exit fullscreen mode

4. makeDebouncer

function makeDebouncer(ms, fn) {
  const [run, clear] = RunAfter(ms, fn)
  const debouncer = (...args) => (
    clear(), run(...args)
  )
  return [debouncer, clear]
}

const [run, clear] = makeDebouncer(
  seconds(1),
  () => log('world')
)

// 4)
run()
run()
run()
// What will be logged?
Enter fullscreen mode Exit fullscreen mode

5. makeThrottler

function makeThrottler(ms, fn) {
  let canRun = true
  const [run, clear] = RunAfter(ms, reset)
  return [throttler, reset]

  function throttler(...args) {
    if (!canRun) return
    canRun = false
    run()
    fn(...args)
  }

  function reset() {
    clear()
    canRun = true
  }
}

const [run, clear] = makeThrottler(
  seconds(1),
  () => log('world')
)

// 5)
run()
run()
run()
clear()
// What will be logged?
Enter fullscreen mode Exit fullscreen mode

If you need a little help, I made a corresponding interactive version of the article that offers some very basic visuals.

I'm not sure if they help intuit what's going on, but they give the answers at least! Did they meet your expectations?

Discussion (0)