From b4d950334052ee5e03f697122f7554ebfba9aeb3 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 24 Oct 2011 13:13:24 -0400 Subject: [PATCH] fixes #170, fixes #266 ... reimplementing throttle in terms of debounce. --- test/functions.js | 20 ++++++++++++++++++-- underscore.js | 42 +++++++++++++++++++++++++----------------- 2 files changed, 43 insertions(+), 19 deletions(-) diff --git a/test/functions.js b/test/functions.js index 05155eca9..5bcf215b7 100644 --- a/test/functions.js +++ b/test/functions.js @@ -90,16 +90,32 @@ $(document).ready(function() { _.delay(function(){ ok(deferred, "deferred the function"); start(); }, 50); }); - asyncTest("functions: throttle", 1, function() { + asyncTest("functions: throttle", 2, function() { var counter = 0; var incr = function(){ counter++; }; var throttledIncr = _.throttle(incr, 100); throttledIncr(); throttledIncr(); throttledIncr(); + setTimeout(throttledIncr, 70); setTimeout(throttledIncr, 120); setTimeout(throttledIncr, 140); + setTimeout(throttledIncr, 190); setTimeout(throttledIncr, 220); setTimeout(throttledIncr, 240); - _.delay(function(){ ok(counter == 3, "incr was throttled"); start(); }, 400); + _.delay(function(){ ok(counter == 1, "incr was called immediately"); }, 30); + _.delay(function(){ ok(counter == 4, "incr was throttled"); start(); }, 400); + }); + + asyncTest("functions: throttle arguments", 2, function() { + var value = 0; + var update = function(val){ value = val; }; + var throttledUpdate = _.throttle(update, 100); + throttledUpdate(1); throttledUpdate(2); throttledUpdate(3); + setTimeout(function(){ throttledUpdate(4); }, 120); + setTimeout(function(){ throttledUpdate(5); }, 140); + setTimeout(function(){ throttledUpdate(6); }, 260); + setTimeout(function(){ throttledUpdate(7); }, 270); + _.delay(function(){ ok(value == 1, "updated to latest value"); }, 40); + _.delay(function(){ ok(value == 7, "updated to latest value"); start(); }, 400); }); asyncTest("functions: debounce", 1, function() { diff --git a/underscore.js b/underscore.js index 72237d180..04f3311cd 100644 --- a/underscore.js +++ b/underscore.js @@ -520,8 +520,29 @@ return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); }; - // Internal function used to implement `_.throttle` and `_.debounce`. - var limit = function(func, wait, debounce) { + // Returns a function, that, when invoked, will only be triggered at most once + // during a given window of time. + _.throttle = function(func, wait) { + var timeout, context, args, throttling, finishThrottle; + finishThrottle = _.debounce(function(){ throttling = false; }, wait); + return function() { + context = this; args = arguments; + var throttler = function() { + timeout = null; + func.apply(context, args); + finishThrottle(); + }; + if (!timeout) timeout = setTimeout(throttler, wait); + if (!throttling) func.apply(context, args); + if (finishThrottle) finishThrottle(); + throttling = true; + }; + }; + + // Returns a function, that, as long as it continues to be invoked, will not + // be triggered. The function will be called after it stops being called for + // N milliseconds. + _.debounce = function(func, wait) { var timeout; return function() { var context = this, args = arguments; @@ -529,24 +550,11 @@ timeout = null; func.apply(context, args); }; - if (debounce) clearTimeout(timeout); - if (debounce || !timeout) timeout = setTimeout(throttler, wait); + clearTimeout(timeout); + timeout = setTimeout(throttler, wait); }; }; - // Returns a function, that, when invoked, will only be triggered at most once - // during a given window of time. - _.throttle = function(func, wait) { - return limit(func, wait, false); - }; - - // Returns a function, that, as long as it continues to be invoked, will not - // be triggered. The function will be called after it stops being called for - // N milliseconds. - _.debounce = function(func, wait) { - return limit(func, wait, true); - }; - // Returns a function that will be executed at most one time, no matter how // often you call it. Useful for lazy initialization. _.once = function(func) {