Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
80742 views
1
var isObject = require('../lang/isObject'),
2
now = require('../date/now');
3
4
/** Used as the `TypeError` message for "Functions" methods. */
5
var FUNC_ERROR_TEXT = 'Expected a function';
6
7
/* Native method references for those with the same name as other `lodash` methods. */
8
var nativeMax = Math.max;
9
10
/**
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
17
* `func` invocation.
18
*
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.
22
*
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`.
25
*
26
* @static
27
* @memberOf _
28
* @category Function
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 is 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.
39
* @example
40
*
41
* // avoid costly calculations while the window size is in flux
42
* jQuery(window).on('resize', _.debounce(calculateLayout, 150));
43
*
44
* // invoke `sendMail` when the click event is fired, debouncing subsequent calls
45
* jQuery('#postbox').on('click', _.debounce(sendMail, 300, {
46
* 'leading': true,
47
* 'trailing': false
48
* }));
49
*
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, {
53
* 'maxWait': 1000
54
* }));
55
*
56
* // cancel a debounced call
57
* var todoChanges = _.debounce(batchLog, 1000);
58
* Object.observe(models.todo, todoChanges);
59
*
60
* Object.observe(models, function(changes) {
61
* if (_.find(changes, { 'user': 'todo', 'type': 'delete'})) {
62
* todoChanges.cancel();
63
* }
64
* }, ['delete']);
65
*
66
* // ...at some point `models.todo` is changed
67
* models.todo.completed = true;
68
*
69
* // ...before 1 second has passed `models.todo` is deleted
70
* // which cancels the debounced `todoChanges` call
71
* delete models.todo;
72
*/
73
function debounce(func, wait, options) {
74
var args,
75
maxTimeoutId,
76
result,
77
stamp,
78
thisArg,
79
timeoutId,
80
trailingCall,
81
lastCalled = 0,
82
maxWait = false,
83
trailing = true;
84
85
if (typeof func != 'function') {
86
throw new TypeError(FUNC_ERROR_TEXT);
87
}
88
wait = wait < 0 ? 0 : (+wait || 0);
89
if (options === true) {
90
var leading = true;
91
trailing = false;
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;
96
}
97
98
function cancel() {
99
if (timeoutId) {
100
clearTimeout(timeoutId);
101
}
102
if (maxTimeoutId) {
103
clearTimeout(maxTimeoutId);
104
}
105
maxTimeoutId = timeoutId = trailingCall = undefined;
106
}
107
108
function delayed() {
109
var remaining = wait - (now() - stamp);
110
if (remaining <= 0 || remaining > wait) {
111
if (maxTimeoutId) {
112
clearTimeout(maxTimeoutId);
113
}
114
var isCalled = trailingCall;
115
maxTimeoutId = timeoutId = trailingCall = undefined;
116
if (isCalled) {
117
lastCalled = now();
118
result = func.apply(thisArg, args);
119
if (!timeoutId && !maxTimeoutId) {
120
args = thisArg = null;
121
}
122
}
123
} else {
124
timeoutId = setTimeout(delayed, remaining);
125
}
126
}
127
128
function maxDelayed() {
129
if (timeoutId) {
130
clearTimeout(timeoutId);
131
}
132
maxTimeoutId = timeoutId = trailingCall = undefined;
133
if (trailing || (maxWait !== wait)) {
134
lastCalled = now();
135
result = func.apply(thisArg, args);
136
if (!timeoutId && !maxTimeoutId) {
137
args = thisArg = null;
138
}
139
}
140
}
141
142
function debounced() {
143
args = arguments;
144
stamp = now();
145
thisArg = this;
146
trailingCall = trailing && (timeoutId || !leading);
147
148
if (maxWait === false) {
149
var leadingCall = leading && !timeoutId;
150
} else {
151
if (!maxTimeoutId && !leading) {
152
lastCalled = stamp;
153
}
154
var remaining = maxWait - (stamp - lastCalled),
155
isCalled = remaining <= 0 || remaining > maxWait;
156
157
if (isCalled) {
158
if (maxTimeoutId) {
159
maxTimeoutId = clearTimeout(maxTimeoutId);
160
}
161
lastCalled = stamp;
162
result = func.apply(thisArg, args);
163
}
164
else if (!maxTimeoutId) {
165
maxTimeoutId = setTimeout(maxDelayed, remaining);
166
}
167
}
168
if (isCalled && timeoutId) {
169
timeoutId = clearTimeout(timeoutId);
170
}
171
else if (!timeoutId && wait !== maxWait) {
172
timeoutId = setTimeout(delayed, wait);
173
}
174
if (leadingCall) {
175
isCalled = true;
176
result = func.apply(thisArg, args);
177
}
178
if (isCalled && !timeoutId && !maxTimeoutId) {
179
args = thisArg = null;
180
}
181
return result;
182
}
183
debounced.cancel = cancel;
184
return debounced;
185
}
186
187
module.exports = debounce;
188
189