diff --git a/build.js b/build.js index 0e99c29fa..3909ebcac 100755 --- a/build.js +++ b/build.js @@ -167,7 +167,7 @@ 'sortedIndex': ['createCallback', 'identity'], 'tap': ['value'], 'template': ['defaults', 'escape', 'escapeStringChar', 'keys', 'values'], - 'throttle': ['isObject'], + 'throttle': ['debounce'], 'times': ['createCallback'], 'toArray': ['isString', 'slice', 'values'], 'transform': ['createCallback', 'createObject', 'forOwn', 'isArray'], @@ -3342,6 +3342,11 @@ // remove `templateSettings` assignment 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')) { source = removeFunction(source, 'chain'); source = removeFunction(source, 'wrapperToString'); diff --git a/build/pre-compile.js b/build/pre-compile.js index d86079827..f7c1ef4ed 100644 --- a/build/pre-compile.js +++ b/build/pre-compile.js @@ -176,6 +176,7 @@ 'leading', 'map', 'max', + 'maxWait', 'memoize', 'merge', 'methods', diff --git a/lodash.js b/lodash.js index c74a96f1d..be0e8f7c7 100644 --- a/lodash.js +++ b/lodash.js @@ -324,27 +324,30 @@ */ function getObject() { return objectPool.pop() || { - 'args': null, + 'args': '', 'array': null, - 'bottom': null, + 'bottom': '', 'criteria': null, - 'false': null, - 'firstArg': null, - 'index': null, - 'init': null, - 'loop': null, - 'null': null, + 'false': false, + 'firstArg': '', + 'index': 0, + 'init': '', + 'leading': false, + 'loop': '', + 'maxWait': 0, + 'null': false, 'number': null, 'object': null, 'push': null, 'shadowedProps': null, 'string': null, 'support': null, - 'top': null, - 'true': null, - 'undefined': null, - 'useHas': null, - 'useKeys': null, + 'top': '', + 'trailing': false, + 'true': false, + 'undefined': false, + 'useHas': false, + 'useKeys': false, 'value': null }; } @@ -4810,6 +4813,7 @@ * @param {Number} wait The number of milliseconds to delay. * @param {Object} options The options object. * [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. * @returns {Function} Returns the new debounced function. * @example @@ -4827,35 +4831,67 @@ result, thisArg, callCount = 0, + lastCalled = 0, + maxWait = false, + maxTimeoutId = null, timeoutId = null, trailing = true; - function delayed() { + function trailingCall() { var isCalled = trailing && (!leading || callCount > 1); - callCount = timeoutId = 0; + callCount = 0; + + clearTimeout(maxTimeoutId); + clearTimeout(timeoutId); + maxTimeoutId = timeoutId = null; + if (isCalled) { + lastCalled = new Date; result = func.apply(thisArg, args); } } + wait = nativeMax(0, wait || 0); if (options === true) { var leading = true; trailing = false; } else if (isObject(options)) { + maxWait = 'maxWait' in options && nativeMax(wait, options.maxWait || 0); leading = options.leading; trailing = 'trailing' in options ? options.trailing : trailing; } return function() { + var now = new Date; + if (!timeoutId && !leading) { + lastCalled = now; + } + var remaining = (maxWait || wait) - (now - lastCalled); args = arguments; thisArg = this; + callCount++; // 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 clearTimeout(timeoutId); + timeoutId = null; - if (leading && ++callCount < 2) { - result = func.apply(thisArg, args); + if (maxWait === false) { + 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; }; } @@ -5056,47 +5092,23 @@ * })); */ function throttle(func, wait, options) { - var args, - result, - thisArg, - lastCalled = 0, - leading = true, - timeoutId = null, + var leading = true, trailing = true; - function trailingCall() { - timeoutId = null; - if (trailing) { - lastCalled = new Date; - result = func.apply(thisArg, args); - } - } if (options === false) { leading = false; } else if (isObject(options)) { leading = 'leading' in options ? options.leading : leading; trailing = 'trailing' in options ? options.trailing : trailing; } - return function() { - var now = new Date; - if (!timeoutId && !leading) { - lastCalled = now; - } - var remaining = wait - (now - lastCalled); - args = arguments; - thisArg = this; + options = getObject(); + options.leading = leading; + options.maxWait = wait; + options.trailing = trailing; - if (remaining <= 0) { - clearTimeout(timeoutId); - timeoutId = null; - lastCalled = now; - result = func.apply(thisArg, args); - } - else if (!timeoutId) { - timeoutId = setTimeout(trailingCall, remaining); - } - return result; - }; + var result = debounce(func, wait, options); + releaseObject(options); + return result; } /** diff --git a/test/test.js b/test/test.js index 2dc8116ec..c21766b79 100644 --- a/test/test.js +++ b/test/test.js @@ -634,6 +634,34 @@ QUnit.start(); }, 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); + }); }()); /*--------------------------------------------------------------------------*/