Skip to the code
cf.

Debounce

EasyTiming

Write debounce(fn, wait). It returns a function that, every time it's called, restarts a timer. When wait ms pass with no new call, it calls fn once, with the this and arguments of the most recent call.

As long as calls keep coming less than wait ms apart, fn doesn't run at all. The returned function also has a cancel() method that drops a pending call.

const save = debounce((text) => console.log("saving", text), 300);
save("h");
save("he");
save("hel");
// 300 ms after the last call: logs "saving hel", once

What the tests check

  1. returns a function
    expect(typeof debounce(() => {}, 100)).toBe("function");
    
  2. calls fn once, wait ms after the last call
    const clock = useFakeTimers();
    const fn = spy();
    const debounced = debounce(fn, 100);
    debounced();
    clock.tick(50);
    debounced();
    clock.tick(99);
    expect(fn).toHaveBeenCalledTimes(0);
    clock.tick(1);
    expect(fn).toHaveBeenCalledTimes(1);
    
  3. never calls fn while calls keep coming
    const clock = useFakeTimers();
    const fn = spy();
    const debounced = debounce(fn, 100);
    for (let i = 0; i < 10; i++) {
      debounced();
      clock.tick(90);
    }
    expect(fn).toHaveBeenCalledTimes(0);
    clock.tick(10);
    expect(fn).toHaveBeenCalledTimes(1);
    
  4. passes the latest arguments
    const clock = useFakeTimers();
    const fn = spy();
    const debounced = debounce(fn, 100);
    debounced("a");
    debounced("b", 2);
    clock.tick(100);
    expect(fn).toHaveBeenCalledTimes(1);
    expect(fn).toHaveBeenLastCalledWith("b", 2);
    
  5. keeps the caller's this
    const clock = useFakeTimers();
    const fn = spy();
    const box = { save: debounce(fn, 10) };
    box.save();
    clock.tick(10);
    expect(fn.calls[0].this).toBe(box);
    
  6. calls again after a later burst
    const clock = useFakeTimers();
    const fn = spy();
    const debounced = debounce(fn, 100);
    debounced(1);
    clock.tick(100);
    debounced(2);
    clock.tick(100);
    expect(fn).toHaveBeenCalledTimes(2);
    expect(fn).toHaveBeenLastCalledWith(2);
    
  7. cancel() drops the pending call
    const clock = useFakeTimers();
    const fn = spy();
    const debounced = debounce(fn, 100);
    debounced();
    debounced.cancel();
    clock.tick(200);
    expect(fn).not.toHaveBeenCalled();
    

Learn more

solution.jsSaved in this browser

Run tests runs every test on your code, here in your browser. The problem is solved when they all pass.

Ctrl + Enter runs the tests (⌘ + Enter on a Mac).