1 var isObject = require('../lang/isObject'),
2 now = require('../date/now');
4 /** Used as the `TypeError` message for "Functions" methods. */
5 var FUNC_ERROR_TEXT = 'Expected a function';
7 /* Native method references for those with the same name as other `lodash` methods. */
8 var nativeMax = Math.max;
11 * Creates a debounced function that delays invoking `func` until after `wait`
12 * milliseconds have elapsed since the last time the debounced function was
13 * invoked. The debounced function comes with a `cancel` method to cancel
14 * delayed invocations. Provide an options object to indicate that `func`
15 * should be invoked on the leading and/or trailing edge of the `wait` timeout.
16 * Subsequent calls to the debounced function return the result of the last
19 * **Note:** If `leading` and `trailing` options are `true`, `func` is invoked
20 * on the trailing edge of the timeout only if the the debounced function is
21 * invoked more than once during the `wait` timeout.
23 * See [David Corbacho's article](http://drupalmotion.com/article/debounce-and-throttle-visual-explanation)
24 * for details over the differences between `_.debounce` and `_.throttle`.
29 * @param {Function} func The function to debounce.
30 * @param {number} [wait=0] The number of milliseconds to delay.
31 * @param {Object} [options] The options object.
32 * @param {boolean} [options.leading=false] Specify invoking on the leading
33 * edge of the timeout.
34 * @param {number} [options.maxWait] The maximum time `func` is allowed to be
35 * delayed before it's invoked.
36 * @param {boolean} [options.trailing=true] Specify invoking on the trailing
37 * edge of the timeout.
38 * @returns {Function} Returns the new debounced function.
41 * // avoid costly calculations while the window size is in flux
42 * jQuery(window).on('resize', _.debounce(calculateLayout, 150));
44 * // invoke `sendMail` when the click event is fired, debouncing subsequent calls
45 * jQuery('#postbox').on('click', _.debounce(sendMail, 300, {
50 * // ensure `batchLog` is invoked once after 1 second of debounced calls
51 * var source = new EventSource('/stream');
52 * jQuery(source).on('message', _.debounce(batchLog, 250, {
56 * // cancel a debounced call
57 * var todoChanges = _.debounce(batchLog, 1000);
58 * Object.observe(models.todo, todoChanges);
60 * Object.observe(models, function(changes) {
61 * if (_.find(changes, { 'user': 'todo', 'type': 'delete'})) {
62 * todoChanges.cancel();
66 * // ...at some point `models.todo` is changed
67 * models.todo.completed = true;
69 * // ...before 1 second has passed `models.todo` is deleted
70 * // which cancels the debounced `todoChanges` call
73 function debounce(func, wait, options) {
85 if (typeof func != 'function') {
86 throw new TypeError(FUNC_ERROR_TEXT);
88 wait = wait < 0 ? 0 : (+wait || 0);
89 if (options === true) {
92 } else if (isObject(options)) {
93 leading = !!options.leading;
94 maxWait = 'maxWait' in options && nativeMax(+options.maxWait || 0, wait);
95 trailing = 'trailing' in options ? !!options.trailing : trailing;
100 clearTimeout(timeoutId);
103 clearTimeout(maxTimeoutId);
106 maxTimeoutId = timeoutId = trailingCall = undefined;
109 function complete(isCalled, id) {
113 maxTimeoutId = timeoutId = trailingCall = undefined;
116 result = func.apply(thisArg, args);
117 if (!timeoutId && !maxTimeoutId) {
118 args = thisArg = undefined;
124 var remaining = wait - (now() - stamp);
125 if (remaining <= 0 || remaining > wait) {
126 complete(trailingCall, maxTimeoutId);
128 timeoutId = setTimeout(delayed, remaining);
132 function maxDelayed() {
133 complete(trailing, timeoutId);
136 function debounced() {
140 trailingCall = trailing && (timeoutId || !leading);
142 if (maxWait === false) {
143 var leadingCall = leading && !timeoutId;
145 if (!maxTimeoutId && !leading) {
148 var remaining = maxWait - (stamp - lastCalled),
149 isCalled = remaining <= 0 || remaining > maxWait;
153 maxTimeoutId = clearTimeout(maxTimeoutId);
156 result = func.apply(thisArg, args);
158 else if (!maxTimeoutId) {
159 maxTimeoutId = setTimeout(maxDelayed, remaining);
162 if (isCalled && timeoutId) {
163 timeoutId = clearTimeout(timeoutId);
165 else if (!timeoutId && wait !== maxWait) {
166 timeoutId = setTimeout(delayed, wait);
170 result = func.apply(thisArg, args);
172 if (isCalled && !timeoutId && !maxTimeoutId) {
173 args = thisArg = undefined;
177 debounced.cancel = cancel;
181 module.exports = debounce;