Custom throttling function six steps to deal with complex requirements

Posted by stuartbates on Sat, 12 Feb 2022 14:23:31 +0100

Throttling definition

Some frequently operated events will affect performance. "Throttling" is used to control the response interval. When the event is triggered, the corresponding function will not be triggered immediately, but will execute the response function according to a specific time interval every time the response interval is reached.

Throttling case

In the "aircraft war" in the online game, the keyboard keys can be used to launch bullets and tap the keyboard quickly and continuously. The aircraft will not launch continuously, but control the distance between bullets at a certain time interval. For example, if the system is set to fire bullets once a second, even if you hit the keyboard 20 times in a second, only one bullet will be sent.

Throttling usage scenario

In the process of programming, many scenarios can use "throttling".

  • Frequent input and search in the input box
  • Click the button frequently and submit information to trigger an event
  • Listen for browser scrolling events
  • Listen for browser zoom events

When throttling is not used

Here, a commodity search box is simulated. We need to call the interface for association query on the content entered by the user to give the user search tips.
When anti shake is not used, we will directly bind the function to the corresponding event.

// html
<input />

// js code
const inputEl = document.querySelector("input");

let count = 0;
function inputEvent(event) {
    console.log(`${++count}Input times, the obtained content is:${event?.target?.value}`);
}
inputEl.oninput = inputEvent;

Enter "JavaScript csses6" in the input box, a total of 16 characters, so the method was called 16 times

The performance of this method is very low, because the interface is called every time a character is input, which causes great pressure on the server. The function of "throttling" executes the function according to the specified time, avoiding the waste of resources caused by multiple executions.

Self determined throttle function

The principle of throttling function implementation is to execute the function according to the specified time interval.

Step 1: basic version throttling implementation

Judge the time interval between the current and the last execution of the function. If the specified time interval is exceeded, the function will be executed.

function throttle(fn, interval) {
  // Set the initial time to 0
  let startTime = 0;
  
  const _throttle = function () {
    // Get current time
    let currentTime = new Date().getTime();
    // Gets the remaining time (the distance between the current time and the specified interval)
    let restTime = interval - (currentTime - startTime);
    
    // When the remaining time is less than or equal to 0, execute the function
    if (restTime <= 0) {
      // Execute the passed in function
      fn();
      // Assign the current time to the initial time
      startTime = currentTime;
    }
  };
  return _throttle;
}

inputEl.oninput = throttle(inputEvent, 2000);

The time interval specified here is 2 seconds, that is, the function is executed once in 2 seconds.

But at this time, we found that the parameter was not passed, and in fact, the direction of this is wrong

Step 2: expand this and parameters

Use the apply method to change the direction of this and pass parameters

function throttle(fn, interval) {
  // Set the initial time to 0
  let startTime = 0;
  
  const _throttle = function (...args) {
    // Get current time
    let currentTime = new Date().getTime();
    // Gets the remaining time (the distance between the current time and the specified interval)
    let restTime = interval - (currentTime - startTime);
    
    // When the remaining time is less than or equal to 0, execute the function
    if (restTime <= 0) {
      // Change the direction and transfer parameters of this through apply
      fn.apply(this, args);
      // Assign the current time to the initial time
      startTime = currentTime;
    }
  };
  return _throttle;
}

At this point, both this and parameters can be obtained~

So far, most usage scenarios of throttling have been realized, and the following functions will be more complex.

Step 3: the function executes immediately

In the above function definition, when the first character is input, the function will be executed with a high probability, because the time of inputting the first character minus the initialization time of 0 seconds is generally greater than the set time interval.

If you don't think it is necessary to execute the function when you enter the first character, you can customize parameters to control whether the function will be executed immediately.

The parameter leading controls the immediate execution of the function, which defaults to true.

function throttle(fn, interval, options = {}) {
  let startTime = 0;
  // The default value of leading is set to true
  const { leading = true } = options ;
  
  const _throttle = function (...args) {
    let currentTime = new Date().getTime();
    
    // When immediate execution is not required, modify the startTime with the initial value of 0 to the current time
    if (!leading && !startTime) {
      startTime = currentTime;
    }
    let restTime = interval - (currentTime - startTime);
    
    if (restTime <= 0) {
      fn.apply(this, args);
      startTime = currentTime;
    }
  };
  return _throttle;
}

// Pass in the leading parameter
 inputEl.oninput = throttle(inputEvent, 2000, {
    leading: false,
 });

This will wait for 2s before the first function call is executed

Step 4: the last execution of the function

"Throttling" is only related to the interval of the function, and has nothing to do with the completion of the last character input.

Therefore, if the time interval between the last character input and the last function call does not reach the specified time interval, the function will not be executed at this time. If you need to execute, you need to customize parameters to control function execution.

The last execution of the function is controlled by the parameter trailing, which is false by default. When the function needs to be executed at the last time, set the timer before the execution of each time interval. When the function is executed at the time interval, clear the timer. If the specified interval is not reached after the last character is entered, execute the contents of the timer.

function throttle(fn, interval, options = {}) {
  let startTime = 0;
  // Set a timer
  let timer = null;
  // The default value of leading is set to true, and the default value of trailing is set to false
  const { leading = true, trailing = false } = options;
  
  const _throttle = function (...args) {
    let currentTime = new Date().getTime();
    
    // When immediate execution is not required, modify the startTime with the initial value of 0 to the current time
    if (!leading && !startTime) {
      startTime = currentTime;
    }
    let restTime = interval - (currentTime - startTime);
    
    if (restTime <= 0) {
      // When there is a timer, clear the timer
      if (timer) {
        clearTimeout(timer);
        timer = null;
      }
      fn.apply(this, args);
      startTime = currentTime;
      // When the execution is completed, the following timer code will not be executed to avoid repeated execution
      return;
    }
    
    // If the last execution is required
    if (trailing && !timer) {
      // Set timer
      timer = setTimeout(() => {
        timer = null;
        fn.apply(this, args);
        // When immediate execution is required, the start time is assigned as the current time; otherwise, it is assigned as 0
        startTime = !leading ? 0 : new Date().getTime();
      }, restTime);
    }
  };
  return _throttle;
}

// Pass in the leading and trailing parameters
inputEl.oninput = throttle(inputEvent, 2000, {
    leading: false,
    trailing: true,
});

At this time, after entering the last character, wait for the time interval (restTime) set in the timer, and the function will execute again.

Step 5: cancel the function

There may be such a scenario. When the user clicks Cancel when searching, or closes the page, there is no need to send the request again.
We add a cancel button and click to terminate the operation.

// html
<input />
<button>cancel</button>

// javascript
function throttle(fn, interval, options = {}) {
  let startTime = 0;
  // Set a timer
  let timer = null;
  // The default value of leading is set to true, and the default value of trailing is set to false
  const { leading = true, trailing = false } = options;
  
  const _throttle = function (...args) {
    let currentTime = new Date().getTime();
    // When immediate execution is not required, modify the startTime with the initial value of 0 to the current time
    if (!leading && !startTime) {
      startTime = currentTime;
    }
    let restTime = interval - (currentTime - startTime);
    
    if (restTime <= 0) {
      // When there is a timer, clear the timer
      if (timer) {
        clearTimeout(timer);
        timer = null;
      }
      fn.apply(this, args);
      startTime = currentTime;
      // When the execution is completed, the following timer code will not be executed to avoid repeated execution
      return;
    }
    
    // If the last execution is required
    if (trailing && !timer) {
      // Set timer
      timer = setTimeout(() => {
        timer = null;
        fn.apply(this, args);
        // When immediate execution is required, the start time is assigned as the current time; otherwise, it is assigned as 0
        startTime = !leading ? 0 : new Date().getTime();
      }, restTime);
    }
  };
  
  // Define a cancellation method on the function object
  _throttle.cancel = function () {
    // When there is a timer, it is cleared
    if (timer) {
      clearTimeout(timer);
      timer = null;
      // Reset start time
      startTime = 0;
    }
  };
  return _throttle;
}

// Get dom element
const inputEl = document.querySelector("input");
const cancelBtn = document.querySelector("button");

const _throttle = throttle(inputEvent, 2000, {
    leading: false,
    trailing: true,
});

// Binding event
inputEl.oninput = _throttle;
cancelBtn.onclick = _throttle.cancel;

When you click Cancel, the contents of the timer will not be executed

Step 6: function return value

After the above "throttling" function is executed, there is no return value. If the return value is required, there are two forms.

Callback function
Get the return value by passing the callback function in the parameter.

function throttle(fn, interval, options = {}) {
  let startTime = 0;
  // Set a timer
  let timer = null;
  // The default value of leading is set to true, the default value of trailing is set to false, and the incoming callback function is used to receive the return value
  const { leading = true, trailing = false, callbackFn } = options;
  const _throttle = function (...args) {
    let currentTime = new Date().getTime();
    // When immediate execution is not required, modify the startTime with the initial value of 0 to the current time
    if (!leading && !startTime) {
      startTime = currentTime;
    }
    let restTime = interval - (currentTime - startTime);
    if (restTime <= 0) {
      // When there is a timer, clear the timer
      if (timer) {
        clearTimeout(timer);
        timer = null;
      }
      // Gets the result of executing the function
      const result = fn.apply(this, args);
      // Execute the incoming callback function
      if (callbackFn) callbackFn(result);
      startTime = currentTime;
      // When the execution is completed, the following timer code will not be executed to avoid repeated execution
      return;
    }
    if (trailing && !timer) {
      timer = setTimeout(() => {
        timer = null;
        // Gets the result of executing the function
        const result = fn.apply(this, args);
        // Execute the incoming callback function
        if (callbackFn) callbackFn(result);
        // When immediate execution is required, the start time is assigned as the current time; otherwise, it is assigned as 0
        startTime = !leading ? 0 : new Date().getTime();
      }, restTime);
    }
  };
  // Define a cancellation method on the function object
  _throttle.cancel = function () {
    if (timer) {
      // When there is a timer, it is cleared
      clearTimeout(timer);
      timer = null;
      // Reset start time
      startTime = 0;
    }
  };
  return _throttle;
}

const inputEl = document.querySelector("input");
const cancelBtn = document.querySelector("button");

// The incoming callback function is used to receive the return value
const _throttle = throttle(inputEvent, 2000, {
    leading: false,
    trailing: true,
    callbackFn: (value) => {
     console.log("Get return value", value);
    },
});
inputEl.oninput = _throttle;
cancelBtn.onclick = _throttle.cancel;

Each time the response function is executed, the callback function is executed.

promise
Get the return value by returning promise

function throttle(fn, interval, options = {}) {
  let startTime = 0;
  // Set a timer
  let timer = null;
  // The default value of leading is set to true, and the default value of trailing is set to false
  const { leading = true, trailing = false } = options;
  const _throttle = function (...args) {
    // Return results through promise
    return new Promise((resolve, reject) => {
      let currentTime = new Date().getTime();
      // When immediate execution is not required, modify the startTime with the initial value of 0 to the current time
      if (!leading && !startTime) {
        startTime = currentTime;
      }
      let restTime = interval - (currentTime - startTime);
      if (restTime <= 0) {
        // When there is a timer, clear the timer
        if (timer) {
          clearTimeout(timer);
          timer = null;
        }
        // Gets the result of executing the function
        const result = fn.apply(this, args);
        // A successful response is returned through resolve
        resolve(result);
        startTime = currentTime;
        return;
      }
      if (trailing && !timer) {
        timer = setTimeout(() => {
          timer = null;
          // Gets the result of executing the function
          const result = fn.apply(this, args);
          // A successful response is returned through resolve
          resolve(result);
          // When immediate execution is required, the start time is assigned as the current time; otherwise, it is assigned as 0
          startTime = !leading ? 0 : new Date().getTime();
        }, restTime);
      }
    });
  };
  // Define a cancellation method on the function object
  _throttle.cancel = function () {
    if (timer) {
      // When there is a timer, it is cleared
      clearTimeout(timer);
      timer = null;
      // Reset start time
      startTime = 0;
    }
  };
  return _throttle;
}

// Get dom element
const inputEl = document.querySelector("input");
const cancelBtn = document.querySelector("button");

const _throttle = throttle(inputEvent, 2000, {
    leading: false,
    trailing: true,
});

// apply is used to point this to the input element
const promiseCallback = function (...args) {
    _throttle.apply(inputEl, args).then((res) => {
     console.log("promise Callback", res);
    });
};

// Binding event
inputEl.oninput = promiseCallback;
cancelBtn.onclick = _throttle.cancel;

promise calls the then method to get the return value

In development, the throttling function is used to optimize the performance of the project. It can be customized as above, or a third-party library can be used.

For the anti shake function, please refer to this article, Five steps of user-defined anti shake function to meet complex requirements

The above is the related content of anti shake function. There are still many places that developers need to master about js advanced. You can see other blog posts I wrote and keep updating~

Topics: Javascript Front-end Optimize