This is the 117th original article without water, if you want to get more original good articles, please search our official account to follow us. This article was first published in the front blog of political Business Cloud: Anti-shake throttling scenarios and applications

Anti-shake throttling scenario and application

background

In daily development, we often encounter search queries that trigger changes in Input values and continue to trigger function calls as the user enters. Or if we listen for window scrolling to send a buried request while the user is swiping through an item on the product search page, the interface calls will be triggered frequently. But sometimes we don’t want the interface to be invoked frequently during the user’s continuous operation. In order to limit the occurrence of such high-frequency function calls in a short time, we can use anti-shake and throttling.

Function stabilization and throttling optimize function execution efficiency by controlling event trigger frequency. Let’s take a visual look at the difference between conventional, anti-shake and throttling through the following figure.

The horizontal axis in the figure is the time axis. It can be seen that the anti-shake method is to execute the callback after the event stops firing for a period of time, and the throttling method is to execute the callback at a certain time interval when the event continues firing.

Analysis of the anti-shake and throttling scenarios

Image stabilization

Anti-shake, as the name suggests, to prevent shaking. It is used to convert user action trigger to program action trigger to prevent jitter of user operation results. The callback is executed only once in a period of time when the event is executed multiple times within a specified interval of n seconds.

Features: Wait for an operation to stop, then interval the operation

  • Continuous trigger non-execution
  • Does not trigger for a period of time before executing

Application Scenarios:

Mousemove Mouse slide event

// It is not executed immediately for the first time
function debounce(func, wait) {
    let timer;

    return function () {
        const context = this;
        const args = arguments;

        clearTimeout(timer);
        timer = setTimeout(function(){ func.apply(context, args) }, wait); }}function getUserAction(e) {
  // container is a sample code container
  container.innerHTML = `${e.clientX}.${e.clientY}`;
};

container.onmousemove = debounce(getUserAction, 1000);
Copy the code

The anti-shake effect is not added as shown in the figure. The x and Y coordinates are constantly changing in the process of mouse sliding.

As shown in the picture after anti-shake effect is added, x and Y axis coordinates are updated 1000ms after the mouse stops sliding.

Let’s take a look at a common business example, Select to the server’s dynamic search function. The difference with the above scenario is whether it is executed the first time

// Execute the first function immediately, show the user the default m pieces of data, wait until the user manually input to stop triggering n seconds, then execute again
function debounce(func, wait, immediate) {
    let timer;
  	let localImmediate = immediate;

    return function () {
        const context = this;
        const args = arguments;

        if (localImmediate) {
          // The flag is to indicate whether the first execution is immediate
          localImmediate = false;
          func.apply(context, args);
        }
      	clearTimeout(timer);
        timer = setTimeout(function(){ func.apply(context, args) }, wait); }}function fetchData(vaule) {
   // Call the interface to request data
}
debounce(fetchData, n);
Copy the code
  • The above is realized through the anti-shake method, after the user stops input for n seconds, the user goes to the server to request data, but it may trigger the first search after the user enters Hangzhou. The city stadium was then entered, triggering a second search.

    There are two possible scenarios displayed on the page:

    • The first result returns faster than the second and is displayed firsthangzhouSearch results, and then displayHangzhou GymnasiumSearch results. The effect is as follows:
    • The first result returns slower than the second and is displayed firstHangzhou GymnasiumSearch results, and then displayhangzhouSearch results. The effect is as follows:

    In fact, neither the first case nor the second case is very good, we hope that it will directly display a hangzhou stadium search results.

    So how to deal with it? We can simply set a variable to mark the last request and only show the result to the user if the token requested by the current interface is equal to the latest token.

/** * Update the global variable this.lastFetchId and assign the value to the internal variable fetchId each time the fetchData method is called. * The internal logic of each fetchData method determines whether the internal variable fetchId is equal to the global variable * this.lastFetchId after the interface returns successfully. * /

// Global variable that marks the latest request ID, updated each time fetchData is called
this.lastFetchId = 0; 
function fetchData(value) {
    const { searchField, params = {}, url, dataKey } = this.props;
  	const [data, setData] = useState([]);
  	const [fetching, setFetching] = useState(false);
  
    this.lastFetchId += 1;
  	// The internal variable fetchId for each method call
    const fetchId = this.lastFetchId;

  	setData([]);
  	setFetching(true);
 
    const postValue = typeof value === 'string' ? value : ' ';
    params[searchField] = postValue;
  
    request(url, {
      method: 'post'.data: params,
    }).then((res) = > {
      const { success, result } = res || {};
      // If it is not the latest request, no result assignment is performed
      if(fetchId ! = =this.lastFetchId) {
        return;
      }
      if (success && Array.isArray(result)) {
        setData(result);
  			setFetching(false); }}); };Copy the code

As can be seen from the above examples, the anti-shake method avoids mistaking an operation as multiple operations, and limits the upper limit of event execution, that is, to execute the event n seconds after the trigger is stopped. The same scenario may include login, registration and other form submission operations, such as multiple requests triggered by users clicking too fast, and real-time saving of editing content such as rich text editor emails.

The throttle

Throttling, as the name suggests, controls flow. It is used to control the frequency of events when the user interacts with the page. In general, the operation is performed periodically in a unit of time or other intervals. For a period of time, the event fires once every time it reaches our specified interval of n seconds.

Features: After waiting for a certain interval, the operation

  • Continuous triggering does not occur more than once
  • To a certain time/other interval (such as sliding height) to execute

Application scenarios (Note: The following examples are related to the business content of the company, so the actual screen shots are not displayed) :

  • Buried scene. Commodity search list, commodity window, etc., the user sliding time/fixed sliding height to send buried point request
If the event is executed for the first time after n seconds, it will be executed again after the event stops triggering
// If the trigger stops at 6.8s, then the trigger is executed at 6s.
// The execution will continue for the last time at 7s
function throttle(func, wait) {
    var timer;
    return function() {
        var context = this;
        var args = arguments;
   
        if(! timer) { timer =setTimeout(function(){
                timer = null;
                func.apply(context, args)
            }, wait)
        }

    }
}
function sendData(vaule) {
   // Call the interface to send data
}
throttle(sendData, n);
Copy the code

As shown, buried requests are sent at fixed intervals

  • The O&M system refreshes application run logs every n seconds
If the event is executed for the first time after n seconds, the event will not be executed again after it stops triggering
If the trigger stops on 6.8s, the last time will be executed on 6s, after which it will not be executed again
function throttle(func, wait) {
    var previous = 0;

    return function() {
        // Implicit conversion
        var now = +new Date(a);var context = this;
        var args = arguments;
        if(now - previous > wait) { previous = now; func.apply(context, args); }}}function fetchLogData(vaule) {
   // Call the interface to get log data
}
throttle(fetchLogData, n);
Copy the code

As shown, pull run logs at fixed intervals

As you can see from the above example, throttling controls the frequency of event firing and limits the upper and lower limits of event execution, that is, every n seconds during event firing. The same scenario might include more frequently triggered events such as Scroll Mousemove, browser progress bar location calculation, input dynamic search, etc.

Lodash anti – throttling source code analysis

The basic implementation and application scenarios of anti-shake throttling are introduced above for our understanding and use. In actual business scenarios, we will choose mature third-party libraries to achieve the effect of shaking prevention and throttling. We take a look at Lodash and Underscore. Js and so on.

Anti-shock: The core idea of Lodash is not to frequently manage the timer, but to implement shouldInvoke to determine if func should be executed, and to cancel the timer only when the provided cancel method cances the delay.

The internal implementation logic of shouldInvoke, which is described in detail in the function execution module below, is called in timer switches and entry functions to determine whether a func function should be executed. We can see some of the comments in the source code below. We disassemble into four modules for analysis: basic definition, timer switch, function execution, external callback.

Basic Definition (including overall structure)

The following is the overall code structure for Lodash to implement shock protection. The entry function defines some timers and function execution variables. There are 10 variables in total, among which 7 time-related variables, maxWait, timerId, lastCallTime, lastInvokeTime, leading, Maxing and trailing, are important support for the realization of timer switch and function execution module.

import isObject from './isObject.js'
import root from './.internal/root.js'

function debounce(func, wait, options) {
  /** ====== Basic definition ====== */
  
  let lastArgs, // Last time I will execute the arguments of debmentioning
    lastThis, // Last time this
    maxWait, // Maximum wait time, ensure that greater than the set maximum interval will be executed, for throttling effect
    result, // The return value of the function func
    timerId, // Timer ID
    lastCallTime // The last time debounce was called

  let lastInvokeTime = 0 // The last time func was executed for throttling effect
  let leading = false // First trigger before delay
  let maxing = false // Whether maxWait is set to implement throttling effect
  let trailing = true // Last trigger after delay

  // Bypass `requestAnimationFrame` by explicitly setting `wait=0`.
  constuseRAF = (! wait && wait ! = =0 && typeof root.requestAnimationFrame === 'function')

  if (typeoffunc ! = ='function') {
    throw new TypeError('Expected a function')}// Implicit conversion
  wait = +wait || 0
  * function isObject(value) {* const type = typeof value * return value! = null && (type == 'object' || type == 'function') * } */
  if(isObject(options)) { leading = !! options.leading maxing ='maxWait' in options
    // maxWait takes the maximum value of maxWait and wait. To implement throttling, ensure that maxWait is greater than wait
    maxWait = maxing ? Math.max(+options.maxWait || 0, wait) : maxWait
    trailing = 'trailing' inoptions ? !!!!! options.trailing : trailing }/** ====== Timer switch ====== */
  
  // Set the timer
  function startTimer(pendingFunc, wait) {}

  // Cancel the timer
  function cancelTimer(id) {}

  // Calculate the waiting time
  function remainingWait(time) {}

  // Timer callback
  function timerExpired() {}
  
  
  /** ====== executes ====== */
  
  / / delay before
  function leadingEdge(time) {}

  // Callback after delay
  function trailingEdge(time) {}
  
  // Execute the func function
  function invokeFunc(time) {}
  
  // Determine whether the func function should be executed at this point
  function shouldInvoke(time) {}
  

  /** ====== external callback ====== */
  
  // Cancel the delay
  function cancel() {}

  // Call immediately
  function flush() {}

  // Check whether the timer is in
  function pending() {}

  // the entry function
  function debounced(. args) {}
  debounced.cancel = cancel
  debounced.flush = flush
  debounced.pending = pending
  return debounced
}

export default debounce
Copy the code

Timer switch

	/** ====== Timer switch ====== */
  
  // Set the timer
  function startTimer(pendingFunc, wait) {
    if (useRAF) {
      / / not Settings set to wait or wait for 0 at the window call. RequestAnimationFrame ().
      // Requires the browser to call the specified callback function to update the animation before the next redraw
      root.cancelAnimationFrame(timerId)
      return root.requestAnimationFrame(pendingFunc)
    }
    return setTimeout(pendingFunc, wait)
  }

  // Cancel the timer
  function cancelTimer(id) {
    if (useRAF) {
      return root.cancelAnimationFrame(id)
    }
    clearTimeout(id)
  }

  // Calculate the waiting time
  function remainingWait(time) {
    // The interval between the current time and the last call to debounce
    const timeSinceLastCall = time - lastCallTime
    // The interval between the current time and the last func execution
    const timeSinceLastInvoke = time - lastInvokeTime
    // Remaining waiting time
    const timeWaiting = wait - timeSinceLastCall

    // Whether the maximum wait time is set (whether throttling is set)
    // No: remaining waiting time
    // Yes: the remaining wait time and the minimum interval between the current time and the last func execution
      
    return maxing
      ? Math.min(timeWaiting, maxWait - timeSinceLastInvoke)
      : timeWaiting
  }

  // Timer callback
  function timerExpired() {
    const time = Date.now()
    // Perform postponement callback when func should be executed
    if (shouldInvoke(time)) {
      return trailingEdge(time)
    }
    // Calculate the waiting time and reset the timer
    timerId = startTimer(timerExpired, remainingWait(time))
  }
  
Copy the code

Function performs

  /** ====== executes ====== */
  
  / / delay before
  function leadingEdge(time) {
    // Sets the last time the func function was executed
    lastInvokeTime = time
    // Set the timer
    timerId = startTimer(timerExpired, wait)
    // Execute func immediately if leading is set
    return leading ? invokeFunc(time) : result
  }

  // Callback after delay
  function trailingEdge(time) {
    timerId = undefined

    // trailing continues to trigger once after the trailing delay
    // lastArgs marks debounce as having been executed at least once
    if (trailing && lastArgs) {
      return invokeFunc(time)
    }
    // Reset parameters
    lastArgs = lastThis = undefined
    return result
  }
  
  // Execute the func function
  function invokeFunc(time) {
    const args = lastArgs
    const thisArg = lastThis

    lastArgs = lastThis = undefined
    lastInvokeTime = time
    result = func.apply(thisArg, args)
    return result
  }
  
  // Determine whether the func function should be executed at this point
  function shouldInvoke(time) {
    // The interval between the current time and the last call to debounce
    const timeSinceLastCall = time - lastCallTime
    // The interval between the current time and the last func execution
    const timeSinceLastInvoke = time - lastInvokeTime

    // First call
    // The wait interval is exceeded
    // The system time has changed
    // The maximum waiting time maxWait is exceeded
    return (lastCallTime === undefined || (timeSinceLastCall >= wait) ||
      (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait))
  }
  
Copy the code

Foreign callback

 /** ====== external callback ====== */

  // Cancel the delay
  function cancel() {
    // Cancel the timer
    if(timerId ! = =undefined) {
      cancelTimer(timerId)
    }
    // Reset parameters
    lastInvokeTime = 0
    lastArgs = lastCallTime = lastThis = timerId = undefined
  }

  // Call immediately
  function flush() {
    return timerId === undefined ? result : trailingEdge(Date.now())
  }

  // Check whether the timer is in
  function pending() {
    returntimerId ! = =undefined
  }
Copy the code

Throttling: The implementation of the throttling function in Lodash is simple. It directly calls the anti-shake function and sets the input parameter maxWait to achieve throttling effect.

function throttle(func, wait, options) {
  let leading = true
  let trailing = true

  if (typeoffunc ! = ='function') {
    throw new TypeError('Expected a function')}if (isObject(options)) {
    leading = 'leading' inoptions ? !!!!! options.leading : leading trailing ='trailing' inoptions ? !!!!! options.trailing : trailing }return debounce(func, wait, {
    leading,
    trailing,
    'maxWait': wait
  })
}

export default throttle
Copy the code

The above is a brief analysis of the anti-shake and throttling implementation of Lodash. In actual business scenarios, you can use the anti-shake and throttling methods provided by Lodash directly. If you need more customized functions that may not be implemented or do not support configuration, you can consider implementing them based on your understanding of the source code to meet actual business requirements.

The difference between anti-shake and throttling depends on the actual service scenario

Visual comparison, online view (note: from Stuart – function tremble and function throttling)

Anti-shake may be used for unpredictable user initiatives, such as users entering content to dynamically search for results on the server. The speed at which users type is unpredictable and irregular.

Throttling may be used for some non-user active behaviors or predictable user active behaviors, such as sending buried request when users slide merchandise window, sliding fixed height is known logic, with regularity.

conclusion

Using the idea of anti-shake and throttling, to control the timing of function execution, can save performance, avoid page lag and other bad user experience. The concepts of anti-shake and throttling are similar and difficult to distinguish. The above content has been introduced from the perspective of the author’s own understanding of anti-shake and throttling.

While beginners may see a different understanding and introduction in JavaScript Advanced Programming or some other technical article by the author, You might see arguments and arguments that throttle in JavaScript Advanced Programming is debounce, that dynamic search should be defused, and real-time search should be throttled. We hope that after you have your own understanding of anti-shake and throttling, you can decide to use anti-shake and throttling according to the actual application scenarios and requirements, and choose a more reasonable and appropriate method.

reference

Stuart – function anti – shake and function throttling

Lodash anti – shake throttling source code understanding

Function anti – shake and throttle is what??

Underscore for JavaScript topics

JavaScript topics follow the science of underscore

How are Lodash anti-shake and throttling implemented

Recommended reading

E-commerce minimum inventory – SKU and algorithm implementation

What you need to know about project management

How to build code global retrieval system from 0 to 1

How to build a build deployment platform suitable for your team

Open source works

  • Political cloud front-end tabloid

Open source address www.zoo.team/openweekly/ (wechat communication group on the official website of tabloid)

, recruiting

ZooTeam, a young passionate and creative front-end team, belongs to the PRODUCT R&D department of ZooTeam, based in picturesque Hangzhou. The team now has more than 50 front-end partners, with an average age of 27, and nearly 30% of them are full-stack engineers, no problem in the youth storm group. The members consist of “old” soldiers from Alibaba and netease, as well as fresh graduates from Zhejiang University, University of Science and Technology of China, Hangzhou Electric And other universities. In addition to daily business docking, the team also carried out technical exploration and practice in material system, engineering platform, building platform, performance experience, cloud application, data analysis and visualization, promoted and implemented a series of internal technical products, and continued to explore the new boundary of front-end technology system.

If you want to change what’s been bothering you, you want to start bothering you. If you want to change, you’ve been told you need more ideas, but you don’t have a solution. If you want change, you have the power to make it happen, but you don’t need it. If you want to change what you want to accomplish, you need a team to support you, but you don’t have the position to lead people. If you want to change the pace, it will be “5 years and 3 years of experience”; If you want to change the original savvy is good, but there is always a layer of fuzzy window… If you believe in the power of believing, believing that ordinary people can achieve extraordinary things, believing that you can meet a better version of yourself. If you want to be a part of the process of growing a front end team with deep business understanding, sound technology systems, technology value creation, and impact spillover as your business takes off, I think we should talk. Any time, waiting for you to write something and send it to [email protected]