Timers
Loading "Timers"
Timers 
Run locally for transcripts
Debounce and throttle have to be some of my favorite utility functions, and they both happen to rely on timers to work. Let's study them in more detail and also see how we would test them.
Take a look at the 
debounce function implementation below:/**
 * Debounce the given callback function.
 */
export function debounce<Args extends Array<unknown>>(
  callback: (...args: Args) => unknown,
  duration: number,
): (...args: Args) => void {
  let timeout: NodeJS.Timeout | null = null
  return function (...args) {
    const effect = () => {
      timeout = null
      return callback.apply(this, args)
    }
    clearTimeout(timeout)
    timeout = setTimeout(effect, duration)
  }
}
The intention behind the 
debounce() function is to accept a callback and keep delaying its execution until a certain time duration has passed since its last call.For example, consider this usage scenario:
// Create a debounced function of "foo"
// with the debounce duration of 250ms.
const bar = debounce(foo, 250)
bar('one')
bar('two')
bar('three')
Here, the 
bar() function is just a debounced wrapped over foo(). Not all calls to bar() will translate to the calls of foo() (hence, the whole point of debouncing). Here's what's happening in the example above:- bar('one')gets called, starting a 250ms timer for- foo('one').
- bar('two')gets called, cancelling the previous timer and starting a new one for- foo('two').
- bar('three')gets called, cancelling the previous timer and starting a new one for- foo('three').
- After 250ms pass without any calls to bar(), thefoo('three')finally gets called.
Let's try putting this very use case into an automated test:
test('executes the callback after the debounce timeout', () => {
  const foo = vi.fn<(input: string) => void>()
  const bar = debounce(foo, 250)
  bar('one')
  bar('two')
  bar('three')
  expect(foo).toHaveBeenCalledOnce()
  expect(foo).toHaveBeenCalledWith('three')
})
Running this test, however, will produce something unexpected:
 FAIL  debounce.test.ts > executes the callback after the debounce timeout
AssertionError: expected "spy" to be called once, but got 0 times
 ❯ debounce.test.ts:19:15
     7|   bar('three')
     8|
     9|   expect(foo).toHaveBeenCalledOnce()
The 
foo() function is not called at all!Our assertions throw because they run immediately after the debounced calls. We aren't taking the debounce timeout (250ms) into account at all. I think it's time we did.
You already know how to mock date and time using the 
vi.useFakeTimers() and vi.setSystemTime() methods, but this exercise doesn't just require you to mock them, it requires you to advance the time.