time-to-botec

Benchmark sampling in different programming languages
Log | Files | Refs | README

debounce.js (6100B)


      1 var isObject = require('./isObject'),
      2     now = require('./now'),
      3     toNumber = require('./toNumber');
      4 
      5 /** Error message constants. */
      6 var FUNC_ERROR_TEXT = 'Expected a function';
      7 
      8 /* Built-in method references for those with the same name as other `lodash` methods. */
      9 var nativeMax = Math.max,
     10     nativeMin = Math.min;
     11 
     12 /**
     13  * Creates a debounced function that delays invoking `func` until after `wait`
     14  * milliseconds have elapsed since the last time the debounced function was
     15  * invoked. The debounced function comes with a `cancel` method to cancel
     16  * delayed `func` invocations and a `flush` method to immediately invoke them.
     17  * Provide `options` to indicate whether `func` should be invoked on the
     18  * leading and/or trailing edge of the `wait` timeout. The `func` is invoked
     19  * with the last arguments provided to the debounced function. Subsequent
     20  * calls to the debounced function return the result of the last `func`
     21  * invocation.
     22  *
     23  * **Note:** If `leading` and `trailing` options are `true`, `func` is
     24  * invoked on the trailing edge of the timeout only if the debounced function
     25  * is invoked more than once during the `wait` timeout.
     26  *
     27  * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
     28  * until to the next tick, similar to `setTimeout` with a timeout of `0`.
     29  *
     30  * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
     31  * for details over the differences between `_.debounce` and `_.throttle`.
     32  *
     33  * @static
     34  * @memberOf _
     35  * @since 0.1.0
     36  * @category Function
     37  * @param {Function} func The function to debounce.
     38  * @param {number} [wait=0] The number of milliseconds to delay.
     39  * @param {Object} [options={}] The options object.
     40  * @param {boolean} [options.leading=false]
     41  *  Specify invoking on the leading edge of the timeout.
     42  * @param {number} [options.maxWait]
     43  *  The maximum time `func` is allowed to be delayed before it's invoked.
     44  * @param {boolean} [options.trailing=true]
     45  *  Specify invoking on the trailing edge of the timeout.
     46  * @returns {Function} Returns the new debounced function.
     47  * @example
     48  *
     49  * // Avoid costly calculations while the window size is in flux.
     50  * jQuery(window).on('resize', _.debounce(calculateLayout, 150));
     51  *
     52  * // Invoke `sendMail` when clicked, debouncing subsequent calls.
     53  * jQuery(element).on('click', _.debounce(sendMail, 300, {
     54  *   'leading': true,
     55  *   'trailing': false
     56  * }));
     57  *
     58  * // Ensure `batchLog` is invoked once after 1 second of debounced calls.
     59  * var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 });
     60  * var source = new EventSource('/stream');
     61  * jQuery(source).on('message', debounced);
     62  *
     63  * // Cancel the trailing debounced invocation.
     64  * jQuery(window).on('popstate', debounced.cancel);
     65  */
     66 function debounce(func, wait, options) {
     67   var lastArgs,
     68       lastThis,
     69       maxWait,
     70       result,
     71       timerId,
     72       lastCallTime,
     73       lastInvokeTime = 0,
     74       leading = false,
     75       maxing = false,
     76       trailing = true;
     77 
     78   if (typeof func != 'function') {
     79     throw new TypeError(FUNC_ERROR_TEXT);
     80   }
     81   wait = toNumber(wait) || 0;
     82   if (isObject(options)) {
     83     leading = !!options.leading;
     84     maxing = 'maxWait' in options;
     85     maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait;
     86     trailing = 'trailing' in options ? !!options.trailing : trailing;
     87   }
     88 
     89   function invokeFunc(time) {
     90     var args = lastArgs,
     91         thisArg = lastThis;
     92 
     93     lastArgs = lastThis = undefined;
     94     lastInvokeTime = time;
     95     result = func.apply(thisArg, args);
     96     return result;
     97   }
     98 
     99   function leadingEdge(time) {
    100     // Reset any `maxWait` timer.
    101     lastInvokeTime = time;
    102     // Start the timer for the trailing edge.
    103     timerId = setTimeout(timerExpired, wait);
    104     // Invoke the leading edge.
    105     return leading ? invokeFunc(time) : result;
    106   }
    107 
    108   function remainingWait(time) {
    109     var timeSinceLastCall = time - lastCallTime,
    110         timeSinceLastInvoke = time - lastInvokeTime,
    111         timeWaiting = wait - timeSinceLastCall;
    112 
    113     return maxing
    114       ? nativeMin(timeWaiting, maxWait - timeSinceLastInvoke)
    115       : timeWaiting;
    116   }
    117 
    118   function shouldInvoke(time) {
    119     var timeSinceLastCall = time - lastCallTime,
    120         timeSinceLastInvoke = time - lastInvokeTime;
    121 
    122     // Either this is the first call, activity has stopped and we're at the
    123     // trailing edge, the system time has gone backwards and we're treating
    124     // it as the trailing edge, or we've hit the `maxWait` limit.
    125     return (lastCallTime === undefined || (timeSinceLastCall >= wait) ||
    126       (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait));
    127   }
    128 
    129   function timerExpired() {
    130     var time = now();
    131     if (shouldInvoke(time)) {
    132       return trailingEdge(time);
    133     }
    134     // Restart the timer.
    135     timerId = setTimeout(timerExpired, remainingWait(time));
    136   }
    137 
    138   function trailingEdge(time) {
    139     timerId = undefined;
    140 
    141     // Only invoke if we have `lastArgs` which means `func` has been
    142     // debounced at least once.
    143     if (trailing && lastArgs) {
    144       return invokeFunc(time);
    145     }
    146     lastArgs = lastThis = undefined;
    147     return result;
    148   }
    149 
    150   function cancel() {
    151     if (timerId !== undefined) {
    152       clearTimeout(timerId);
    153     }
    154     lastInvokeTime = 0;
    155     lastArgs = lastCallTime = lastThis = timerId = undefined;
    156   }
    157 
    158   function flush() {
    159     return timerId === undefined ? result : trailingEdge(now());
    160   }
    161 
    162   function debounced() {
    163     var time = now(),
    164         isInvoking = shouldInvoke(time);
    165 
    166     lastArgs = arguments;
    167     lastThis = this;
    168     lastCallTime = time;
    169 
    170     if (isInvoking) {
    171       if (timerId === undefined) {
    172         return leadingEdge(lastCallTime);
    173       }
    174       if (maxing) {
    175         // Handle invocations in a tight loop.
    176         clearTimeout(timerId);
    177         timerId = setTimeout(timerExpired, wait);
    178         return invokeFunc(lastCallTime);
    179       }
    180     }
    181     if (timerId === undefined) {
    182       timerId = setTimeout(timerExpired, wait);
    183     }
    184     return result;
    185   }
    186   debounced.cancel = cancel;
    187   debounced.flush = flush;
    188   return debounced;
    189 }
    190 
    191 module.exports = debounce;