git-off

git off handles large files in git repos
git clone https://noulin.net/git/git-off.git
Log | Files | Refs | README

debounce.js (6042B)


      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         result = wait - timeSinceLastCall;
    112 
    113     return maxing ? nativeMin(result, maxWait - timeSinceLastInvoke) : result;
    114   }
    115 
    116   function shouldInvoke(time) {
    117     var timeSinceLastCall = time - lastCallTime,
    118         timeSinceLastInvoke = time - lastInvokeTime;
    119 
    120     // Either this is the first call, activity has stopped and we're at the
    121     // trailing edge, the system time has gone backwards and we're treating
    122     // it as the trailing edge, or we've hit the `maxWait` limit.
    123     return (lastCallTime === undefined || (timeSinceLastCall >= wait) ||
    124       (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait));
    125   }
    126 
    127   function timerExpired() {
    128     var time = now();
    129     if (shouldInvoke(time)) {
    130       return trailingEdge(time);
    131     }
    132     // Restart the timer.
    133     timerId = setTimeout(timerExpired, remainingWait(time));
    134   }
    135 
    136   function trailingEdge(time) {
    137     timerId = undefined;
    138 
    139     // Only invoke if we have `lastArgs` which means `func` has been
    140     // debounced at least once.
    141     if (trailing && lastArgs) {
    142       return invokeFunc(time);
    143     }
    144     lastArgs = lastThis = undefined;
    145     return result;
    146   }
    147 
    148   function cancel() {
    149     if (timerId !== undefined) {
    150       clearTimeout(timerId);
    151     }
    152     lastInvokeTime = 0;
    153     lastArgs = lastCallTime = lastThis = timerId = undefined;
    154   }
    155 
    156   function flush() {
    157     return timerId === undefined ? result : trailingEdge(now());
    158   }
    159 
    160   function debounced() {
    161     var time = now(),
    162         isInvoking = shouldInvoke(time);
    163 
    164     lastArgs = arguments;
    165     lastThis = this;
    166     lastCallTime = time;
    167 
    168     if (isInvoking) {
    169       if (timerId === undefined) {
    170         return leadingEdge(lastCallTime);
    171       }
    172       if (maxing) {
    173         // Handle invocations in a tight loop.
    174         timerId = setTimeout(timerExpired, wait);
    175         return invokeFunc(lastCallTime);
    176       }
    177     }
    178     if (timerId === undefined) {
    179       timerId = setTimeout(timerExpired, wait);
    180     }
    181     return result;
    182   }
    183   debounced.cancel = cancel;
    184   debounced.flush = flush;
    185   return debounced;
    186 }
    187 
    188 module.exports = debounce;