Debouncing

  • If a function is called multiple times, then if it is debounced then it will wait for specified time and then execute the last call.
  • In other words if a function is debounced for 1000ms then after multiple calls, if there exists time lag of greater than 1000ms then only the function will get executed, else it will not
  • OR in other words, execute this function only if 100 milliseconds have passed without it being called
  • Uses:
    • while typing text to search, we don’t want to send too many calls to server
    • suggestive text, user types something and we suggest the text like google search
    • scrolling, user scrolls too much, the scroll event should not fire too much
[function fired]
f()    f()      f()         
---------------------------------------------------
|      |        |                              |
|      |        |                              |
|      |        |                              |
0ms   200ms    500ms                          1500ms
<200ms>
       <-300ms->
                <-----------1000ms------------>
                                               f() [Actual Execution]

Debouncing Decorator

  • We can create debouncing decorator and enable debouncing to any function:
function debounce(f, timespan) {
  let timeoutId;
  return function (...args) {
    if (timeoutId) {
      // Not the first time firing
      clearTimeout(timeoutId);
    }
 
    timeoutId = setTimeout(() => {
      f.apply(this, args);
    }, timespan);
  };
}
 
let f = debounce(console.log, 1000);
 
f("a");
setTimeout(() => f("b"), 200);
setTimeout(() => f("c"), 500);
setTimeout(() => f("d"), 1700);
setTimeout(() => f("e"), 1900);
 
// Should print c
// Should print e
  • Using lodash in React.js
import {useCallback} from 'react';
import _debounce from 'lodash/debounce';
import axios from 'axios';
 
function Input() {
    const [value, setValue] = useState('');
 
    const debounceFn = useCallback(_debounce(handleDebounceFn, 1000), []);
 
    function handleDebounceFn(inputValue) {
        axios.post('/endpoint', {
          value: inputValue,
        }).then((res) => {
          console.log(res.data);
        });
    }
 
 
    function handleChange (event) {
        setValue(event.target.value);
        debounceFn(event.target.value);
    };
 
    return <input value={value} onChange={handleChange} />
}

Throttling

  • Enforces a maximum number of times a function can be called over time.
  • Execute this function at most once every 100 milliseconds.
  • Good for regular updates that shouldn’t be very often.
  • In throttling with 1000ms:
    1. The first call will always happen
    2. It will start waiting for 1000ms and ignore all calls in this period
    3. After 1000ms it will execute the last call (apart from first call)
    4. Go to Step 2
  • Use:
    • Tracking Mouse movements
    • Wait until the user stops resizing the window

Throttling Decorator

 

Throttle vs Debounce

  • Throttle Rate (of executions per unit of time)
  • Debounce Wait (time before execution happens)

Delay Decorator

  • Decorator to delay the execution of given function by some time
function delay(f, timespan) {
  return function (...args) {
    setTimeout(() => {
      f.apply(this, args);
    }, timespan);
  };
}
 
let f1000 = delay(console.log, 1000);
let f1500 = delay(console.log, 1500);
 
f1000("test"); // shows "test" after 1000ms
f1500("test"); // shows "test" after 1500ms

Spy Decorator

  • Create a decorator spy(func) that should return a wrapper that saves all calls to function in its calls property.
function spy(f) {
  function wrapper(...args) {
    wrapper.calls.push(args);
 
    // return is needed if the method needs to return stuff
    return f.apply(this, args);
  }
 
  // After calling spy the returned function should immediately have `calls` property
  wrapper.calls = [];
 
  return wrapper;
}
 
function work(a, b) {
  return a + b;
}
 
let f = spy(work);
console.log(f(1, 2)); // 3
console.log(f(4, 5)); // 9
 
for (let args of f.calls) {
  console.log("call:" + args.join()); // "call:1,2", "call:4,5"
}
 

Caching Decorator

function cachingDecorator(func) {
  let cache = new Map();
  return function (...args) {
    const key = args.join();
    if (cache.has(key)) {
      console.log("Retrieved from cache");
      return cache.get(key);
    }
    let result = func.apply(this, args);
    cache.set(key, result);
    return result;
  };
}
 
// Flexible function to perform summation of any number of arguments
function sum(...args) {
  return args.reduce((prev, cur) => cur + prev, 0);
}
 
let f = cachingDecorator(sum);
 
console.log(f(3, 4)); // 7
console.log(f(2, 3, 4, 5)); // 14
console.log(f(3, 4, 5)); // 12
 
console.log(f(3, 4));
// Retrieved from cache
// 7