JavaScriptmediumJavaScript

Debounce with Immediate Option

Debounce function with leading edge (immediate) and trailing edge options

01

The problem

Need debounce functionality with immediate execution option

02

The solution

JavaScript
function debounce(func, wait, options = {}) {
  const { leading = false, trailing = true, maxWait } = options;
  let timeoutId, lastCallTime, lastInvokeTime, result;
  let lastArgs, lastThis;
  
  function invokeFunc(time) {
    const args = lastArgs;
    const thisArg = lastThis;
    
    lastArgs = lastThis = undefined;
    lastInvokeTime = time;
    result = func.apply(thisArg, args);
    return result;
  }
  
  function leadingEdge(time) {
    lastInvokeTime = time;
    if (leading) {
      return invokeFunc(time);
    }
    return result;
  }
  
  function remainingWait(time) {
    const timeSinceLastCall = time - lastCallTime;
    const timeSinceLastInvoke = time - lastInvokeTime;
    const timeWaiting = wait - timeSinceLastCall;
    
    return maxWait ?
      Math.min(timeWaiting, maxWait - timeSinceLastInvoke) :
      timeWaiting;
  }
  
  function timerExpired() {
    const time = Date.now();
    if (shouldInvoke(time)) {
      return trailingEdge(time);
    }
    timerId = setTimeout(timerExpired, remainingWait(time));
  }
  
  function shouldInvoke(time) {
    const timeSinceLastCall = time - lastCallTime;
    const timeSinceLastInvoke = time - lastInvokeTime;
    
    return (
      lastCallTime === undefined ||
      timeSinceLastCall >= wait ||
      timeSinceLastCall < 0 ||
      (maxWait !== undefined && timeSinceLastInvoke >= maxWait)
    );
  }
  
  function trailingEdge(time) {
    timeoutId = undefined;
    
    if (trailing && lastArgs) {
      return invokeFunc(time);
    }
    lastArgs = lastThis = undefined;
    return result;
  }
  
  function debounced(...args) {
    const time = Date.now();
    const isInvoking = shouldInvoke(time);
    
    lastArgs = args;
    lastThis = this;
    lastCallTime = time;
    
    if (isInvoking) {
      if (timeoutId === undefined) {
        return leadingEdge(lastCallTime);
      }
      if (maxWait !== undefined) {
        clearTimeout(timeoutId);
        timeoutId = setTimeout(timerExpired, wait);
        return invokeFunc(lastCallTime);
      }
    }
    if (timeoutId === undefined) {
      timeoutId = setTimeout(timerExpired, wait);
    }
    return result;
  }
  
  debounced.cancel = function() {
    if (timeoutId !== undefined) {
      clearTimeout(timeoutId);
    }
    lastInvokeTime = 0;
    lastArgs = lastCallTime = lastThis = timeoutId = undefined;
  };
  
  debounced.flush = function() {
    return timeoutId === undefined ? result : trailingEdge(Date.now());
  };
  
  return debounced;
}

03

Parameters

funcfunction

Function to debounce

waitnumber

Wait time in milliseconds

optionsobject

Options object with leading, trailing, maxWait

04

Put it to work

Example
// Debounce with immediate execution
const debouncedSearch = debounce(
  (query) => {
    console.log('Searching for:', query);
  },
  300,
  { leading: true, trailing: true }
);

// Will execute immediately, then wait
debouncedSearch('hello');
debouncedSearch('world');

// Cancel debounced call
debouncedSearch.cancel();

// Force execution
debouncedSearch.flush();