2

I am trying to construct a debounce function definition to give following results. My following code prints the following.

'hi'
'hi'
'hi'
'hi'

I am struggling to figure out how I can leverage interval to contain x as undefined and once this expires again the function returns 'hi'

function debounce(callback, interval) {
  return () =>{
    let x = callback();
    setTimeout(()=>{
    x = callback();
    },interval);
    return x;
  }
}

// Expectation running below statements
function giveHi() { return 'hi'; }
const giveHiSometimes = debounce(giveHi, 3000);

console.log(giveHiSometimes()); // -> 'hi'
setTimeout(function() { console.log(giveHiSometimes()); }, 2000); // -> undefined
setTimeout(function() { console.log(giveHiSometimes()); }, 4000); // -> undefined
setTimeout(function() { console.log(giveHiSometimes()); }, 8000); // -> 'hi'
saif_shines
  • 143
  • 2
  • 12

2 Answers2

2

Make a calledRecently flag, which gets set to true when the timeout callback runs. When the timeout callback runs, set another timeout which runs after the interval is up to reset calledRecently to false:

function debounce(callback, interval) {
  let calledRecently = false;
  let intervalId;
  return () => {
    const result = calledRecently ? undefined : callback();
    calledRecently = true;
    clearInterval(intervalId);
    intervalId = setTimeout(() => {
      calledRecently = false;
    }, interval);
    return result;
  }
}

const giveHiSometimes = debounce(() => 'hi', 3000);

console.log(giveHiSometimes()); // -> 'hi'
setTimeout(function() { console.log(giveHiSometimes()); }, 2000); // -> undefined
setTimeout(function() { console.log(giveHiSometimes()); }, 4000); // -> undefined
setTimeout(function() { console.log(giveHiSometimes()); }, 8000); // -> 'hi'
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
1

It sounds like you're after leading edge (immediate) debouncing where an initial event is fired, but batched events after it are debounced.

There are a few ways of writing this, including setTimeout and Date.now but it all comes down to moving a lastAttempt variable of some sort outside of the closure and not re-triggering the callback unless the cooldown time has passed without any attempts to call the function being made. Writing it without setInterval seems cleaner to me.

I'd also add an ...args parameter to the function returned from your leadingDebounce function. This lets you pass parameters into the function if you want.

const leadingDebounce = (fn, cooldown) => {
  let lastAttempt = 0;
  
  return (...args) => {
    if (Date.now() - lastAttempt > cooldown) {
      fn(...args);
    }

    lastAttempt = Date.now();
  };
};

const debounced = leadingDebounce(() => console.log("hi"), 3000);

debounced();                                      // -> 'hi'
setTimeout(() => console.log(debounced()), 2000); // -> undefined
setTimeout(() => console.log(debounced()), 4000); // -> undefined
setTimeout(debounced, 8000);                      // -> 'hi'
ggorlen
  • 44,755
  • 7
  • 76
  • 106