1 var isObject = require('./isObject'),
2 now = require('./now'),
3 toNumber = require('./toNumber');
5 /** Used as the `TypeError` message for "Functions" methods. */
6 var FUNC_ERROR_TEXT = 'Expected a function';
8 /* Built-in method references for those with the same name as other `lodash` methods. */
9 var nativeMax = Math.max;
12 * Creates a debounced function that delays invoking `func` until after `wait`
13 * milliseconds have elapsed since the last time the debounced function was
14 * invoked. The debounced function comes with a `cancel` method to cancel
15 * delayed `func` invocations and a `flush` method to immediately invoke them.
16 * Provide an options object to indicate whether `func` should be invoked on
17 * the leading and/or trailing edge of the `wait` timeout. The `func` is invoked
18 * with the last arguments provided to the debounced function. Subsequent calls
19 * to the debounced function return the result of the last `func` invocation.
21 * **Note:** If `leading` and `trailing` options are `true`, `func` is invoked
22 * on the trailing edge of the timeout only if the debounced function is
23 * invoked more than once during the `wait` timeout.
25 * See [David Corbacho's article](http://drupalmotion.com/article/debounce-and-throttle-visual-explanation)
26 * for details over the differences between `_.debounce` and `_.throttle`.
31 * @param {Function} func The function to debounce.
32 * @param {number} [wait=0] The number of milliseconds to delay.
33 * @param {Object} [options] The options object.
34 * @param {boolean} [options.leading=false] Specify invoking on the leading
35 * edge of the timeout.
36 * @param {number} [options.maxWait] The maximum time `func` is allowed to be
37 * delayed before it's invoked.
38 * @param {boolean} [options.trailing=true] Specify invoking on the trailing
39 * edge of the timeout.
40 * @returns {Function} Returns the new debounced function.
43 * // Avoid costly calculations while the window size is in flux.
44 * jQuery(window).on('resize', _.debounce(calculateLayout, 150));
46 * // Invoke `sendMail` when clicked, debouncing subsequent calls.
47 * jQuery(element).on('click', _.debounce(sendMail, 300, {
52 * // Ensure `batchLog` is invoked once after 1 second of debounced calls.
53 * var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 });
54 * var source = new EventSource('/stream');
55 * jQuery(source).on('message', debounced);
57 * // Cancel the trailing debounced invocation.
58 * jQuery(window).on('popstate', debounced.cancel);
60 function debounce(func, wait, options) {
73 if (typeof func != 'function') {
74 throw new TypeError(FUNC_ERROR_TEXT);
76 wait = toNumber(wait) || 0;
77 if (isObject(options)) {
78 leading = !!options.leading;
79 maxWait = 'maxWait' in options && nativeMax(toNumber(options.maxWait) || 0, wait);
80 trailing = 'trailing' in options ? !!options.trailing : trailing;
85 clearTimeout(timeoutId);
88 clearTimeout(maxTimeoutId);
91 args = maxTimeoutId = thisArg = timeoutId = trailingCall = undefined;
94 function complete(isCalled, id) {
98 maxTimeoutId = timeoutId = trailingCall = undefined;
101 result = func.apply(thisArg, args);
102 if (!timeoutId && !maxTimeoutId) {
103 args = thisArg = undefined;
109 var remaining = wait - (now() - stamp);
110 if (remaining <= 0 || remaining > wait) {
111 complete(trailingCall, maxTimeoutId);
113 timeoutId = setTimeout(delayed, remaining);
118 if ((timeoutId && trailingCall) || (maxTimeoutId && trailing)) {
119 result = func.apply(thisArg, args);
125 function maxDelayed() {
126 complete(trailing, timeoutId);
129 function debounced() {
133 trailingCall = trailing && (timeoutId || !leading);
135 if (maxWait === false) {
136 var leadingCall = leading && !timeoutId;
138 if (!lastCalled && !maxTimeoutId && !leading) {
141 var remaining = maxWait - (stamp - lastCalled),
142 isCalled = remaining <= 0 || remaining > maxWait;
146 maxTimeoutId = clearTimeout(maxTimeoutId);
149 result = func.apply(thisArg, args);
151 else if (!maxTimeoutId) {
152 maxTimeoutId = setTimeout(maxDelayed, remaining);
155 if (isCalled && timeoutId) {
156 timeoutId = clearTimeout(timeoutId);
158 else if (!timeoutId && wait !== maxWait) {
159 timeoutId = setTimeout(delayed, wait);
163 result = func.apply(thisArg, args);
165 if (isCalled && !timeoutId && !maxTimeoutId) {
166 args = thisArg = undefined;
170 debounced.cancel = cancel;
171 debounced.flush = flush;
175 module.exports = debounce;