Add maxWait option to _.debounce and implement _.throttle by way of _.debounce. [closes #285]

Former-commit-id: 63b41aac298e5fa89f7922e84b2ed0d5c6545bd3
This commit is contained in:
John-David Dalton
2013-06-10 11:16:14 -07:00
parent c20d7f9754
commit 1933a76631
4 changed files with 97 additions and 51 deletions

View File

@@ -167,7 +167,7 @@
'sortedIndex': ['createCallback', 'identity'], 'sortedIndex': ['createCallback', 'identity'],
'tap': ['value'], 'tap': ['value'],
'template': ['defaults', 'escape', 'escapeStringChar', 'keys', 'values'], 'template': ['defaults', 'escape', 'escapeStringChar', 'keys', 'values'],
'throttle': ['isObject'], 'throttle': ['debounce'],
'times': ['createCallback'], 'times': ['createCallback'],
'toArray': ['isString', 'slice', 'values'], 'toArray': ['isString', 'slice', 'values'],
'transform': ['createCallback', 'createObject', 'forOwn', 'isArray'], 'transform': ['createCallback', 'createObject', 'forOwn', 'isArray'],
@@ -3342,6 +3342,11 @@
// remove `templateSettings` assignment // remove `templateSettings` assignment
source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *lodash\.templateSettings[\s\S]+?};\n/, ''); source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *lodash\.templateSettings[\s\S]+?};\n/, '');
} }
if (isRemoved(source, 'throttle')) {
_.each(['leading', 'maxWait', 'trailing'], function(prop) {
source = removeFromGetObject(source, prop);
});
}
if (isRemoved(source, 'value')) { if (isRemoved(source, 'value')) {
source = removeFunction(source, 'chain'); source = removeFunction(source, 'chain');
source = removeFunction(source, 'wrapperToString'); source = removeFunction(source, 'wrapperToString');

View File

@@ -176,6 +176,7 @@
'leading', 'leading',
'map', 'map',
'max', 'max',
'maxWait',
'memoize', 'memoize',
'merge', 'merge',
'methods', 'methods',

112
lodash.js
View File

@@ -324,27 +324,30 @@
*/ */
function getObject() { function getObject() {
return objectPool.pop() || { return objectPool.pop() || {
'args': null, 'args': '',
'array': null, 'array': null,
'bottom': null, 'bottom': '',
'criteria': null, 'criteria': null,
'false': null, 'false': false,
'firstArg': null, 'firstArg': '',
'index': null, 'index': 0,
'init': null, 'init': '',
'loop': null, 'leading': false,
'null': null, 'loop': '',
'maxWait': 0,
'null': false,
'number': null, 'number': null,
'object': null, 'object': null,
'push': null, 'push': null,
'shadowedProps': null, 'shadowedProps': null,
'string': null, 'string': null,
'support': null, 'support': null,
'top': null, 'top': '',
'true': null, 'trailing': false,
'undefined': null, 'true': false,
'useHas': null, 'undefined': false,
'useKeys': null, 'useHas': false,
'useKeys': false,
'value': null 'value': null
}; };
} }
@@ -4810,6 +4813,7 @@
* @param {Number} wait The number of milliseconds to delay. * @param {Number} wait The number of milliseconds to delay.
* @param {Object} options The options object. * @param {Object} options The options object.
* [leading=false] A boolean to specify execution on the leading edge of the timeout. * [leading=false] A boolean to specify execution on the leading edge of the timeout.
* [maxWait] The maximum time `func` is allowed to be delayed before it's called.
* [trailing=true] A boolean to specify execution on the trailing edge of the timeout. * [trailing=true] A boolean to specify execution on the trailing edge of the timeout.
* @returns {Function} Returns the new debounced function. * @returns {Function} Returns the new debounced function.
* @example * @example
@@ -4827,35 +4831,67 @@
result, result,
thisArg, thisArg,
callCount = 0, callCount = 0,
lastCalled = 0,
maxWait = false,
maxTimeoutId = null,
timeoutId = null, timeoutId = null,
trailing = true; trailing = true;
function delayed() { function trailingCall() {
var isCalled = trailing && (!leading || callCount > 1); var isCalled = trailing && (!leading || callCount > 1);
callCount = timeoutId = 0; callCount = 0;
clearTimeout(maxTimeoutId);
clearTimeout(timeoutId);
maxTimeoutId = timeoutId = null;
if (isCalled) { if (isCalled) {
lastCalled = new Date;
result = func.apply(thisArg, args); result = func.apply(thisArg, args);
} }
} }
wait = nativeMax(0, wait || 0);
if (options === true) { if (options === true) {
var leading = true; var leading = true;
trailing = false; trailing = false;
} else if (isObject(options)) { } else if (isObject(options)) {
maxWait = 'maxWait' in options && nativeMax(wait, options.maxWait || 0);
leading = options.leading; leading = options.leading;
trailing = 'trailing' in options ? options.trailing : trailing; trailing = 'trailing' in options ? options.trailing : trailing;
} }
return function() { return function() {
var now = new Date;
if (!timeoutId && !leading) {
lastCalled = now;
}
var remaining = (maxWait || wait) - (now - lastCalled);
args = arguments; args = arguments;
thisArg = this; thisArg = this;
callCount++;
// avoid issues with Titanium and `undefined` timeout ids // avoid issues with Titanium and `undefined` timeout ids
// https://github.com/appcelerator/titanium_mobile/blob/3_1_0_GA/android/titanium/src/java/ti/modules/titanium/TitaniumModule.java#L185-L192 // https://github.com/appcelerator/titanium_mobile/blob/3_1_0_GA/android/titanium/src/java/ti/modules/titanium/TitaniumModule.java#L185-L192
clearTimeout(timeoutId); clearTimeout(timeoutId);
timeoutId = null;
if (leading && ++callCount < 2) { if (maxWait === false) {
result = func.apply(thisArg, args); if (leading && callCount < 2) {
result = func.apply(thisArg, args);
}
} else {
if (remaining <= 0) {
clearTimeout(maxTimeoutId);
maxTimeoutId = null;
lastCalled = now;
result = func.apply(thisArg, args);
}
else if (!maxTimeoutId) {
maxTimeoutId = setTimeout(trailingCall, remaining);
}
}
if (wait !== maxWait) {
timeoutId = setTimeout(trailingCall, wait);
} }
timeoutId = setTimeout(delayed, wait);
return result; return result;
}; };
} }
@@ -5056,47 +5092,23 @@
* })); * }));
*/ */
function throttle(func, wait, options) { function throttle(func, wait, options) {
var args, var leading = true,
result,
thisArg,
lastCalled = 0,
leading = true,
timeoutId = null,
trailing = true; trailing = true;
function trailingCall() {
timeoutId = null;
if (trailing) {
lastCalled = new Date;
result = func.apply(thisArg, args);
}
}
if (options === false) { if (options === false) {
leading = false; leading = false;
} else if (isObject(options)) { } else if (isObject(options)) {
leading = 'leading' in options ? options.leading : leading; leading = 'leading' in options ? options.leading : leading;
trailing = 'trailing' in options ? options.trailing : trailing; trailing = 'trailing' in options ? options.trailing : trailing;
} }
return function() { options = getObject();
var now = new Date; options.leading = leading;
if (!timeoutId && !leading) { options.maxWait = wait;
lastCalled = now; options.trailing = trailing;
}
var remaining = wait - (now - lastCalled);
args = arguments;
thisArg = this;
if (remaining <= 0) { var result = debounce(func, wait, options);
clearTimeout(timeoutId); releaseObject(options);
timeoutId = null; return result;
lastCalled = now;
result = func.apply(thisArg, args);
}
else if (!timeoutId) {
timeoutId = setTimeout(trailingCall, remaining);
}
return result;
};
} }
/** /**

View File

@@ -634,6 +634,34 @@
QUnit.start(); QUnit.start();
}, 64); }, 64);
}); });
asyncTest('should work with `maxWait` option', function() {
var limit = 96,
withCount = 0,
withoutCount = 0;
var withMaxWait = _.debounce(function() {
withCount++;
}, 32, { 'maxWait': 64 });
var withoutMaxWait = _.debounce(function() {
withoutCount++;
}, 32);
var start = new Date;
while ((new Date - start) < limit) {
withMaxWait();
withoutMaxWait();
}
strictEqual(withCount, 1);
strictEqual(withoutCount, 0);
setTimeout(function() {
strictEqual(withCount, 2);
strictEqual(withoutCount, 1);
QUnit.start();
}, 64);
});
}()); }());
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/