From 9e3e067f5025dbe5e93ed784f93b233882ca0ffe Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Tue, 30 Nov 2010 11:41:48 -0500 Subject: [PATCH] Initial draft of _.throttle and _.debounce --- test/functions.js | 29 +++++++++++++++++++++++++++-- test/objects.js | 9 --------- underscore.js | 27 +++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 11 deletions(-) diff --git a/test/functions.js b/test/functions.js index eba6309d7..96292b563 100644 --- a/test/functions.js +++ b/test/functions.js @@ -54,19 +54,44 @@ $(document).ready(function() { equals(fastFib(10), 55, 'a memoized version of fibonacci produces identical results'); }); - asyncTest("functions: delay", function() { + asyncTest("functions: delay", 2, function() { var delayed = false; _.delay(function(){ delayed = true; }, 100); _.delay(function(){ ok(!delayed, "didn't delay the function quite yet"); }, 50); _.delay(function(){ ok(delayed, 'delayed the function'); start(); }, 150); }); - asyncTest("functions: defer", function() { + asyncTest("functions: defer", 1, function() { var deferred = false; _.defer(function(bool){ deferred = bool; }, true); _.delay(function(){ ok(deferred, "deferred the function"); start(); }, 50); }); + asyncTest("functions: throttle", 1, function() { + var counter = 0; + var incr = function(){ counter++; }; + var throttledIncr = _.throttle(incr, 50); + throttledIncr(); throttledIncr(); throttledIncr(); + setTimeout(throttledIncr, 60); + setTimeout(throttledIncr, 70); + setTimeout(throttledIncr, 110); + setTimeout(throttledIncr, 120); + _.delay(function(){ ok(counter == 3, "incr was throttled"); start(); }, 180); + }); + + asyncTest("functions: debounce", 1, function() { + var counter = 0; + var incr = function(){ counter++; }; + var debouncedIncr = _.debounce(incr, 50); + debouncedIncr(); debouncedIncr(); debouncedIncr(); + setTimeout(debouncedIncr, 30); + setTimeout(debouncedIncr, 60); + setTimeout(debouncedIncr, 90); + setTimeout(debouncedIncr, 120); + setTimeout(debouncedIncr, 150); + _.delay(function(){ ok(counter == 1, "incr was debounced"); start(); }, 220); + }); + test("functions: wrap", function() { var greet = function(name){ return "hi: " + name; }; var backwards = _.wrap(greet, function(func, name){ return func(name) + ' ' + name.split('').reverse().join(''); }); diff --git a/test/objects.js b/test/objects.js index fa0b56ee6..04577994a 100644 --- a/test/objects.js +++ b/test/objects.js @@ -11,15 +11,6 @@ $(document).ready(function() { }); test("objects: functions", function() { - var expected = ["all", "any", "bind", "bindAll", "breakLoop", "clone", "compact", - "compose", "contains", "defer", "delay", "detect", "each", "every", "extend", "filter", "find", "first", - "flatten", "foldl", "foldr", "forEach", "functions", "head", "identity", "include", - "indexOf", "inject", "intersect", "invoke", "isArguments", "isArray", "isBoolean", "isDate", "isElement", "isEmpty", "isEqual", - "isFunction", "isNaN", "isNull", "isNumber", "isRegExp", "isString", "isUndefined", "keys", "last", "lastIndexOf", "map", "max", - "memoize", "methods", "min", "mixin", "noConflict", "pluck", "range", "reduce", "reduceRight", "reject", "rest", "select", - "size", "some", "sortBy", "sortedIndex", "tail", "tap", "template", "times", "toArray", "uniq", "unique", - "uniqueId", "values", "without", "wrap", "zip"]; - same(expected, _.methods(_), 'provides a sorted list of functions'); var obj = {a : 'dash', b : _.map, c : (/yo/), d : _.reduce}; ok(_.isEqual(['b', 'd'], _.functions(obj)), 'can grab the function names of any passed-in object'); }); diff --git a/underscore.js b/underscore.js index 8689aeb42..db904e6cb 100644 --- a/underscore.js +++ b/underscore.js @@ -419,6 +419,33 @@ return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); }; + // Internal function used to implement `_.throttle` and `_.debounce`. + var limit = function(func, wait, debounce) { + var timeout; + return function() { + var context = this, args = arguments; + var throttler = function() { + timeout = null; + func.apply(context, args); + }; + if (debounce) clearTimeout(timeout); + if (debounce || !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 the first function passed as an argument to the second, // allowing you to adjust arguments, run code before and after, and // conditionally execute the original function.