Implement Debounce
Debouncing is a common technique used to control how often a function is executed. It's particularly useful for events that fire rapidly, like scrolling, resizing, or typing in a search bar. The goal is to ensure a function is only called after a certain period of inactivity, preventing excessive computations.
Problem Description
Your task is to implement a debounce function that takes two arguments:
- A function (
func) to be debounced. - A delay time (
delay) in milliseconds.
The debounce function should return a new function. When this new function is called, it should not immediately execute func. Instead, it should reset a timer. If the new function is called again before the delay has elapsed, the previous timer should be cleared, and a new timer should be started. Only when the delay has passed without any further calls to the new function should func be executed.
The debounced function should also be able to accept any arguments that func would normally receive and pass them along when func is eventually executed. The this context should also be preserved.
Key Requirements:
- The returned function should accept arbitrary arguments and preserve
thiscontext. - The debounced function should only execute the original
funcafter the specifieddelayhas passed without further invocations. - If the debounced function is invoked multiple times within the
delayperiod, the previous pending execution offuncshould be canceled.
Expected Behavior: Imagine a user rapidly clicking a button. With debouncing, the associated action would only trigger once, after the user stops clicking for a brief moment.
Edge Cases:
- What happens if
delayis 0 or negative? - What happens if
functhrows an error? (For this challenge, assumefuncwill execute successfully).
Examples
Example 1:
// Assume a simplified clock with millisecond precision for demonstration
let callCount = 0;
const logMessage = (message) => {
callCount++;
console.log(`(${callCount}) Executed with: ${message}`);
};
const debouncedLog = debounce(logMessage, 100);
// Simulate rapid calls
debouncedLog("first"); // Timer starts, but logMessage is not called yet
// ... 50ms pass ...
debouncedLog("second"); // Previous timer cleared, new timer starts
// ... 70ms pass ...
debouncedLog("third"); // Previous timer cleared, new timer starts
// ... 100ms pass after "third" without further calls ...
// Expected Output (after approximately 220ms from the first call):
// (1) Executed with: third
// Explanation: The "third" call reset the timer. Since no more calls were made for 100ms,
// logMessage was executed with the arguments from the "third" call.
Example 2:
let actionCount = 0;
const performAction = function(item) {
actionCount++;
console.log(`Action performed for: ${item} (Call #${actionCount}). Context: ${this.id}`);
};
const debouncedAction = debounce(performAction, 200);
const contextObject = { id: "context123" };
// Simulate calls with context
debouncedAction.call(contextObject, "itemA"); // Timer starts
// ... 150ms pass ...
debouncedAction.call(contextObject, "itemB"); // Previous timer cleared, new timer starts
// ... 200ms pass after "itemB" without further calls ...
// Expected Output (after approximately 350ms from the first call):
// Action performed for: itemB (Call #1). Context: context123
// Explanation: The "itemB" call reset the timer. After 200ms of inactivity, performAction
// was executed with "itemB" and the correct 'this' context.
Example 3: (Showing cancellation)
let invocationCount = 0;
const costlyOperation = (data) => {
invocationCount++;
console.log(`Costly operation executed with data: ${data}. (Invocation #${invocationCount})`);
};
const debouncedOperation = debounce(costlyOperation, 500);
debouncedOperation("initial data"); // Timer 1 starts
// ... 200ms pass ...
debouncedOperation("updated data"); // Timer 1 cancelled, Timer 2 starts
// ... 300ms pass ... (Total 500ms elapsed since last call)
// No further calls.
// Expected Output (after approximately 700ms from the first call):
// Costly operation executed with data: updated data. (Invocation #1)
// Explanation: The second call to debouncedOperation cancelled the first pending execution.
// The operation was then executed with the data from the second call after its timer expired.
Constraints
- The
delayparameter will be a non-negative integer representing milliseconds. - The
funcparameter will be a valid function. - The implementation should be efficient and avoid unnecessary resource usage.
- The solution should work across different JavaScript environments (e.g., browser, Node.js).
Notes
- You will need a mechanism to schedule and cancel timers. In JavaScript,
setTimeoutandclearTimeoutare the standard tools for this. - Consider how to store the timer ID so that it can be cleared later.
- Think about how to capture and pass the arguments and
thiscontext correctly.