Skip to content

Commit

Permalink
Merge pull request #18 from atuttle/master
Browse files Browse the repository at this point in the history
Debounce like a boss
  • Loading branch information
russplaysguitar committed Dec 29, 2012
2 parents 2e47e7b + 02df564 commit 48e84a1
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 38 deletions.
81 changes: 46 additions & 35 deletions Underscore.cfc
Expand Up @@ -1220,11 +1220,11 @@ component {
/* FUNCTION FUNCTIONS */

/**
* @header _.bind(function, object, [*arguments]) : any
* @header _.bind(function, object, [*arguments]) : function
* @hint Bind a function to a structure, meaning that whenever the function is called, the value of "this" will be the structure. Optionally, bind arguments to the function to pre-fill them, also known as partial application.
* @example "func = function(args, this){ return args.greeting & ': ' & this.name; };<br />func = _.bind(func, {name : 'moe'}, {greeting: 'hi'});<br />func();<br />=> 'hi: moe'"
*/
public any function bind(func, context = {}) {
public function function bind(func, context = {}) {
var boundArgs = _.slice(arguments, 3);

if (!_.isFunction(func)) {
Expand Down Expand Up @@ -1267,11 +1267,11 @@ component {
}

/**
* @header _.memoize(function, [hashFunction]) : any
* @header _.memoize(function, [hashFunction]) : function
* @hint Memoizes a given function by caching the computed result. Useful for speeding up slow-running computations. If passed an optional hashFunction, it will be used to compute the hash key for storing the result, based on the arguments to the original function. The default hashFunction just uses the first argument to the memoized function as the key.
* @example fibonacci = _.memoize(function(n) { return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2); });
*/
public any function memoize(func, hasher) {
public function function memoize(func, hasher) {
var memo = {};
if (!structKeyExists(arguments, 'hasher')) {
arguments.hasher = function(x) {
Expand Down Expand Up @@ -1300,11 +1300,11 @@ component {
}

/*
* @header _.defer(function, arguments) : any
* @header _.defer(function, arguments) : function
* @hint Defers a function, scheduling it to run after the current call stack has cleared.
* @example
*/
public any function defer(func, args) {
public function function defer(func, args) {
// TODO: make sure this works, if it is possible in CF
return _.delay(func, 0, args);
}
Expand All @@ -1313,37 +1313,48 @@ component {
Returns a function, that, when invoked, will only be triggered at most once
during a given window of time.
*/
public any function throttle(func, wait) {
public function function throttle(func, wait) {
// TODO
return;
}

/*
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. If `immediate` is passed, trigger the function on the
leading edge, instead of the trailing.
*/
public any function debounce(func, wait, immediate) {
// TODO: in progress
// var lastInvokeTime = GetTickCount();
// return function () {
// var thisInvokeTime = GetTickCount();
// if (thisInvokeTime - lastInvokeTime > wait) {
// func();
// }
// else {
// }
// lastInvokeTime = GetTickCount();
// };
}

/**
* @header _.once(function) : any
/**
* @header _.debounce(function, wait, immediate) : function
* @hint 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. If immediate is passed, trigger the function on the leading edge, instead of the trailing. (Immediate requires a Wait cooldown period beteween calls.)
* @example keepCalm = _.debounce(function(){}, 300, true);<br/>for (var i = 0; i<10; i++){ keepCalm(); }<br/>=>//function argument is called only once
*/
public function function debounce(func, wait, immediate = false) {
var threadNameBase = '_debounced_' & createUUID() & '_';
var threadCount = 0;
var threadName = '_debounced_x';
var called = false;
var setCalled = function(v){ called = v; };
return function () {
if (structKeyExists(cfthread, threadName) && cfthread[threadName].status neq "completed"){
thread action='terminate' name=threadName;
}
threadCount++;
threadName = threadNameBase & threadCount;
if (!structKeyExists(cfthread, threadName)){
thread action='run' name=threadName wait=wait func=func immediate=immediate setCalled=setCalled called=called {
if (immediate && !called) func();
setCalled(true);
sleep(attributes.wait);
if (!immediate) func();
setCalled(false);
}
}else{
throw;
}
};
}

/**
* @header _.once(function) : function
* @hint Returns a function that will be executed at most one time, no matter how often you call it. Useful for lazy initialization.
* @example i = 0;<br />once = _.once(function () { i = i+1; return i; });<br />once();<br />=> 1<br />once();<br />=> 1
*/
public any function once(func) {
public function function once(func) {
var ran = false;
var memo = {};
return function() {
Expand All @@ -1364,11 +1375,11 @@ component {
}

/**
* @header _.wrap(function, wrapper) : any
* @header _.wrap(function, wrapper) : function
* @hint 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.
* @example "hello = function(name) { return "hello: " & name; };<br />hello = _.wrap(hello, function(func) {<br />return "before, " & func("moe") & ", after";<br />});<br />hello();<br />=> 'before, hello: moe, after'"
*/
public any function wrap(func, wrapper) {
public function function wrap(func, wrapper) {
// TODO make sure this handles arguments correctly
return function() {
arguments.func = func;
Expand All @@ -1377,11 +1388,11 @@ component {
}

/**
* @header _.compose(*functions) : any
* @header _.compose(*functions) : function
* @hint Returns a function that is the composition of a list of functions, each function consumes the return value of the function that follows. In math terms, composing the functions f(), g(), and h() produces f(g(h())).
* @example greet = function(name){ return "hi: " & name; };<br />exclaim = function(statement){ return statement & "!"; };<br />welcome = _.compose(exclaim, greet);<br />welcome('moe');<br />=> 'hi: moe!';
*/
public any function compose() {
public function function compose() {
var funcs = arguments;
return function() {
var args = arguments;
Expand All @@ -1396,7 +1407,7 @@ component {

/**
* @header _.after(count, function) : any
* @hint Returns a function that will only be executed after being called N times.
* @hint Returns a function that will only be executed after being called N times. When count <= 0, the result of calling the function immediately is returned.
* @example "func = function () { writeOutput("hi"); };<br />callFuncAfterTwo = _.after(2, func);<br />callFuncAfterTwo();<br />=> // nothing<br />callFuncAfterTwo();<br />=> 'hi'"
*/
public any function after(times, func) {
Expand Down
8 changes: 5 additions & 3 deletions docs/docGenerator.cfm
Expand Up @@ -8,7 +8,7 @@
arrayFunctions = "first,initial,last,rest,compact,flatten,without,union,intersection,difference,uniq,zip,object,indexOf,lastIndexOf,range,concat,reverse,takeWhile,splice,push,unshift,join,slice";
funcFunctions = "bind,bindAll,memoize,delay,once,after,wrap,compose";
funcFunctions = "bind,bindAll,memoize,delay,once,debounce,after,wrap,compose";
objectFunctions = "keys,values,pairs,invert,functions,extend,pick,omit,defaults,clone,has,isEqual,isEmpty,isArray,isObject,isFunction,isString,isNumber,isBoolean,isDate";
Expand All @@ -24,6 +24,8 @@
{title: "Utilities", list: utilFunctions}
];
crlf = chr(13) & chr(10);
display = function (metadata, functionList) {
var functions = listToArray(functionList);
var funcMetas = _.map(functions, function(v){
Expand All @@ -37,7 +39,7 @@
});
_.each(funcMetas, function(val, key) {
if(structKeyExists(val, "hint")) {
writeOutput("<p id='" & val.name &"'>");
writeOutput("#crlf & crlf#<p id='" & val.name &"'>");
writeOutput("<b class='header'>" & val.name & "</b> ");
if(structKeyExists(val, "header")) {
writeOutput("<code>" & val.header & "</code>");
Expand All @@ -59,7 +61,7 @@
<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
<meta http-equiv="X-UA-Compatible" content="chrome=1" />
<meta name="viewport" content="width=device-width" />
<link rel="stylesheet" type="text/css" href="style.css" />
<link rel="stylesheet" type="text/css" href="index_files/style.css" />
</head>
<body>
<div id="sidebar" class="interface">
Expand Down
File renamed without changes.
58 changes: 58 additions & 0 deletions mxunit_tests/functionsTest.cfc
Expand Up @@ -133,6 +133,64 @@ component extends="mxunit.framework.TestCase" {
assertEquals(0, testAfter(5, 4), "after(N) should not fire unless called N times");
assertEquals(1, testAfter(0, 0), "after(0) should fire immediately");
}

public void function testDebounce_ImmediateFalse(){
var touchCount = 0;
var startTime = getTickCount();
var toucher = function(){
touchCount++;
return touchCount;
};
toucher(); // touchCount==1
assertEquals(1, touchCount, "closure isn't working...");

var lazyToucher = _.debounce(toucher, 100);

for (var i = 0; i < 3; i++){
sleep(36);
lazyToucher();
}

var currentTime = getTickCount() - startTime;
debug("time since first call: " & currentTime & "ms");

sleep(100);
currentTime = getTickCount() - startTime;
debug('total time: ' & currentTime & 'ms');
debug(cfthread);
assertTrue(currentTime >= 208, "not waiting long enough!!!!!!11eleven");
sleep(50);
assertEquals(2, touchCount, "debounce calms overzealous method calling");
}

public void function testDebounce_ImmediateTrue(){
var touchCount = 0;
var startTime = getTickCount();
var toucher = function(){
touchCount++;
return touchCount;
};
toucher(); // touchCount==1
assertEquals(1, touchCount, "closure isn't working...");

var lazyToucher = _.debounce(toucher, 100, true);

for (var i = 0; i < 3; i++){
sleep(36);
lazyToucher();
}

sleep(130); //allow cooldown period

assertEquals(2, touchCount, 'once on the leading edge');

for (var i = 0; i < 3; i++){
sleep(10);
lazyToucher();
}

assertEquals(3, touchCount, 'second time on the leading edge (after cooldown)');
}

public void function setUp() {
variables._ = new underscore.Underscore();
Expand Down

0 comments on commit 48e84a1

Please sign in to comment.