Add a cancel function to debounced functions. [closes #567]

This commit is contained in:
John-David Dalton
2014-05-24 14:39:53 -07:00
parent 0c1c4b08c7
commit 1ce0fffd25
2 changed files with 73 additions and 19 deletions

View File

@@ -5363,6 +5363,7 @@
/**
* Creates a function that will delay the execution of `func` until after
* `wait` milliseconds have elapsed since the last time it was invoked.
* The created function comes with a `cancel` method to cancel delayed calls.
* Provide an options object to indicate that `func` should be invoked on
* the leading and/or trailing edge of the `wait` timeout. Subsequent calls
* to the debounced function will return the result of the last `func` call.
@@ -5384,8 +5385,7 @@
* @example
*
* // avoid costly calculations while the window size is in flux
* var lazyLayout = _.debounce(calculateLayout, 150);
* jQuery(window).on('resize', lazyLayout);
* jQuery(window).on('resize', _.debounce(calculateLayout, 150));
*
* // execute `sendMail` when the click event is fired, debouncing subsequent calls
* jQuery('#postbox').on('click', _.debounce(sendMail, 300, {
@@ -5395,9 +5395,26 @@
*
* // ensure `batchLog` is executed once after 1 second of debounced calls
* var source = new EventSource('/stream');
* source.addEventListener('message', _.debounce(batchLog, 250, {
* jQuery(source).on('message', _.debounce(batchLog, 250, {
* 'maxWait': 1000
* }, false);
*
* // cancelling a debounced call
* var todoChanges = _.debounce(batchLog, 1000);
* Object.observe(models.todo, todoChanges);
*
* Object.observe(models, function(changes) {
* if (_.find(changes, { 'name': 'todo', 'type': 'delete'})) {
* todoChanges.cancel();
* }
* }, ['delete']);
*
* // ...at some point `models.todo` is changed
* models.todo.completed = true;
*
* // ...before 1 second has passed `models.todo` is deleted
* // which cancels the debounced `todoChanges` call
* delete models.todo;
*/
function debounce(func, wait, options) {
var args,
@@ -5423,7 +5440,18 @@
maxWait = 'maxWait' in options && nativeMax(wait, +options.maxWait || 0);
trailing = 'trailing' in options ? options.trailing : trailing;
}
var delayed = function() {
function cancel() {
if (timeoutId) {
clearTimeout(timeoutId);
}
if (maxTimeoutId) {
clearTimeout(maxTimeoutId);
}
maxTimeoutId = timeoutId = trailingCall = undefined;
}
function delayed() {
var remaining = wait - (now() - stamp);
if (remaining <= 0 || remaining > wait) {
if (maxTimeoutId) {
@@ -5441,9 +5469,9 @@
} else {
timeoutId = setTimeout(delayed, remaining);
}
};
}
var maxDelayed = function() {
function maxDelayed() {
if (timeoutId) {
clearTimeout(timeoutId);
}
@@ -5455,9 +5483,9 @@
args = thisArg = null;
}
}
};
}
return function() {
function debounced() {
args = arguments;
stamp = now();
thisArg = this;
@@ -5497,7 +5525,9 @@
args = thisArg = null;
}
return result;
};
}
debounced.cancel = cancel;
return debounced;
}
/**
@@ -5732,11 +5762,12 @@
}
/**
* Creates a function that, when executed, will only call the `func` function
* at most once per every `wait` milliseconds. Provide an options object to
* indicate that `func` should be invoked on the leading and/or trailing edge
* of the `wait` timeout. Subsequent calls to the throttled function will
* return the result of the last `func` call.
* Creates a function that will only call the `func` function at most once
* per every `wait` milliseconds. The created function comes with a `cancel`
* method to cancel delayed calls. Provide an options object to indicate that
* `func` should be invoked on the leading and/or trailing edge of the `wait`
* timeout. Subsequent calls to the throttled function will return the result
* of the last `func` call.
*
* Note: If `leading` and `trailing` options are `true`, `func` will be called
* on the trailing edge of the timeout only if the the throttled function is
@@ -5754,13 +5785,14 @@
* @example
*
* // avoid excessively updating the position while scrolling
* var throttled = _.throttle(updatePosition, 100);
* jQuery(window).on('scroll', throttled);
* jQuery(window).on('scroll', _.throttle(updatePosition, 100));
*
* // execute `renewToken` when the click event is fired, but not more than once every 5 minutes
* jQuery('.interactive').on('click', _.throttle(renewToken, 300000, {
* 'trailing': false
* }));
* var throttled = _.throttle(renewToken, 300000, { 'trailing': false })
* jQuery('.interactive').on('click',);
*
* // cancelling a trailing throttled call
* jQuery(window).on('popstate', throttled.cancel);
*/
function throttle(func, wait, options) {
var leading = true,

View File

@@ -9412,6 +9412,28 @@
QUnit.start();
}
});
asyncTest('_.' + methodName + ' should support cancelling delayed calls', 1, function() {
if (!(isRhino && isModularize)) {
var callCount = 0;
var funced = func(function() {
callCount++;
}, 32, { 'leading': false });
funced();
funced.cancel();
setTimeout(function() {
strictEqual(callCount, 0);
QUnit.start();
}, 64);
}
else {
skipTest();
QUnit.start();
}
});
});
/*--------------------------------------------------------------------------*/