mirror of
https://github.com/whoisclebs/lodash.git
synced 2026-02-12 20:07:49 +00:00
Refactor debounce to simplify, reduce timers, fix bugs.
This commit is contained in:
committed by
John-David Dalton
parent
092f90d2fc
commit
864e14cb20
166
lodash.js
166
lodash.js
@@ -9000,14 +9000,12 @@
|
|||||||
* jQuery(window).on('popstate', debounced.cancel);
|
* jQuery(window).on('popstate', debounced.cancel);
|
||||||
*/
|
*/
|
||||||
function debounce(func, wait, options) {
|
function debounce(func, wait, options) {
|
||||||
var args,
|
var lastArgs,
|
||||||
maxTimeoutId,
|
lastThis,
|
||||||
result,
|
result,
|
||||||
stamp,
|
timerId,
|
||||||
thisArg,
|
lastCallTime = 0,
|
||||||
timeoutId,
|
lastInvokeTime = 0,
|
||||||
trailingCall,
|
|
||||||
lastCalled = 0,
|
|
||||||
leading = false,
|
leading = false,
|
||||||
maxWait = false,
|
maxWait = false,
|
||||||
trailing = true;
|
trailing = true;
|
||||||
@@ -9022,94 +9020,104 @@
|
|||||||
trailing = 'trailing' in options ? !!options.trailing : trailing;
|
trailing = 'trailing' in options ? !!options.trailing : trailing;
|
||||||
}
|
}
|
||||||
|
|
||||||
function cancel() {
|
function invoke(time) {
|
||||||
if (timeoutId) {
|
var args = lastArgs,
|
||||||
clearTimeout(timeoutId);
|
thisArg = lastThis;
|
||||||
}
|
|
||||||
if (maxTimeoutId) {
|
lastArgs = lastThis = undefined;
|
||||||
clearTimeout(maxTimeoutId);
|
lastInvokeTime = time;
|
||||||
}
|
result = func.apply(thisArg, args);
|
||||||
lastCalled = 0;
|
return result;
|
||||||
args = maxTimeoutId = thisArg = timeoutId = trailingCall = undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function complete(isCalled, id) {
|
function leadingEdge() {
|
||||||
if (id) {
|
// Reset any `maxWait` timer.
|
||||||
clearTimeout(id);
|
lastInvokeTime = lastCallTime;
|
||||||
|
// Start the timer to the trailing edge.
|
||||||
|
timerId = setTimeout(timerExpired, wait);
|
||||||
|
// Invoke the leading edge.
|
||||||
|
return leading ? invoke(lastCallTime) : result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function trailingEdge(time) {
|
||||||
|
if (timerId !== undefined) {
|
||||||
|
clearTimeout(timerId);
|
||||||
|
timerId = undefined;
|
||||||
}
|
}
|
||||||
maxTimeoutId = timeoutId = trailingCall = undefined;
|
// Only invoke if we have `lastArgs`, which means there has been a call
|
||||||
if (isCalled) {
|
// to `func` since the last invocation
|
||||||
lastCalled = now();
|
if (trailing && lastArgs) {
|
||||||
result = func.apply(thisArg, args);
|
return invoke(time);
|
||||||
if (!timeoutId && !maxTimeoutId) {
|
}
|
||||||
args = thisArg = undefined;
|
lastArgs = lastThis = undefined;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkTimes(time) {
|
||||||
|
var timeSinceLastInvoke = time - lastInvokeTime,
|
||||||
|
waitTime = time - lastCallTime;
|
||||||
|
|
||||||
|
if (waitTime >= wait) {
|
||||||
|
// Activity has stopped. We are at the trailing edge.
|
||||||
|
trailingEdge(time);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (waitTime < 0) {
|
||||||
|
// The system time has gone backwards. Treat it as the trailing edge.
|
||||||
|
trailingEdge(time);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var shouldInvoke = (maxWait !== false && timeSinceLastInvoke >= maxWait);
|
||||||
|
|
||||||
|
// Restart the timer to the smaller of remaining maxWait and remaining wait.
|
||||||
|
var remainingWait = wait - waitTime;
|
||||||
|
if (maxWait !== false) {
|
||||||
|
remainingWait = nativeMin(remainingWait, maxWait - timeSinceLastInvoke);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
'shouldInvoke': shouldInvoke,
|
||||||
|
'remainingWait': remainingWait
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function timerExpired() {
|
||||||
|
var time = now(),
|
||||||
|
check = checkTimes(time);
|
||||||
|
|
||||||
|
timerId = undefined;
|
||||||
|
if (check !== undefined) {
|
||||||
|
// Restart the timer.
|
||||||
|
timerId = setTimeout(timerExpired, check.remainingWait);
|
||||||
|
if (check.shouldInvoke) {
|
||||||
|
invoke(time);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function delayed() {
|
function cancel() {
|
||||||
var remaining = wait - (now() - stamp);
|
if (timerId !== undefined) {
|
||||||
if (remaining <= 0 || remaining > wait) {
|
clearTimeout(timerId);
|
||||||
complete(trailingCall, maxTimeoutId);
|
|
||||||
} else {
|
|
||||||
timeoutId = setTimeout(delayed, remaining);
|
|
||||||
}
|
}
|
||||||
|
lastArgs = lastThis = timerId = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
function flush() {
|
function flush() {
|
||||||
if ((timeoutId && trailingCall) || (maxTimeoutId && trailing)) {
|
return timerId === undefined ? result : trailingEdge(now());
|
||||||
result = func.apply(thisArg, args);
|
|
||||||
}
|
|
||||||
cancel();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function maxDelayed() {
|
|
||||||
complete(trailing, timeoutId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function debounced() {
|
function debounced() {
|
||||||
args = arguments;
|
lastArgs = arguments;
|
||||||
stamp = now();
|
lastThis = this;
|
||||||
thisArg = this;
|
lastCallTime = now();
|
||||||
trailingCall = trailing && (timeoutId || !leading);
|
|
||||||
|
|
||||||
if (maxWait === false) {
|
if (timerId === undefined) {
|
||||||
var leadingCall = leading && !timeoutId;
|
return leadingEdge();
|
||||||
} else {
|
|
||||||
if (!lastCalled && !maxTimeoutId && !leading) {
|
|
||||||
lastCalled = stamp;
|
|
||||||
}
|
|
||||||
var remaining = maxWait - (stamp - lastCalled);
|
|
||||||
|
|
||||||
var isCalled = (remaining <= 0 || remaining > maxWait) &&
|
|
||||||
(leading || maxTimeoutId);
|
|
||||||
|
|
||||||
if (isCalled) {
|
|
||||||
if (maxTimeoutId) {
|
|
||||||
maxTimeoutId = clearTimeout(maxTimeoutId);
|
|
||||||
}
|
|
||||||
lastCalled = stamp;
|
|
||||||
result = func.apply(thisArg, args);
|
|
||||||
}
|
|
||||||
else if (!maxTimeoutId) {
|
|
||||||
maxTimeoutId = setTimeout(maxDelayed, remaining);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (isCalled && timeoutId) {
|
// Check the current times to handle invocations in a tight loop.
|
||||||
timeoutId = clearTimeout(timeoutId);
|
var check = checkTimes(lastCallTime);
|
||||||
}
|
return (check && check.shouldInvoke)
|
||||||
else if (!timeoutId && wait !== maxWait) {
|
? invoke(lastCallTime)
|
||||||
timeoutId = setTimeout(delayed, wait);
|
: result;
|
||||||
}
|
|
||||||
if (leadingCall) {
|
|
||||||
isCalled = true;
|
|
||||||
result = func.apply(thisArg, args);
|
|
||||||
}
|
|
||||||
if (isCalled && !timeoutId && !maxTimeoutId) {
|
|
||||||
args = thisArg = undefined;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
debounced.cancel = cancel;
|
debounced.cancel = cancel;
|
||||||
debounced.flush = flush;
|
debounced.flush = flush;
|
||||||
|
|||||||
78
test/test.js
78
test/test.js
@@ -4193,6 +4193,76 @@
|
|||||||
}, 192);
|
}, 192);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
QUnit.test('should honor leading: false when maxWait is not supplied', function(assert) {
|
||||||
|
assert.expect(6);
|
||||||
|
|
||||||
|
var done = assert.async();
|
||||||
|
|
||||||
|
var callCount = 0;
|
||||||
|
|
||||||
|
var debounced = _.debounce(function(value) {
|
||||||
|
++callCount;
|
||||||
|
return value;
|
||||||
|
}, 32);
|
||||||
|
|
||||||
|
// Leading should not fire.
|
||||||
|
var actual = [debounced(0), debounced(1), debounced(2)];
|
||||||
|
assert.deepEqual(actual, [undefined, undefined, undefined]);
|
||||||
|
assert.strictEqual(callCount, 0);
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
// Trailing should fire by now.
|
||||||
|
assert.strictEqual(callCount, 1);
|
||||||
|
|
||||||
|
// Do it again.
|
||||||
|
var actual = [debounced(4), debounced(5), debounced(6)];
|
||||||
|
|
||||||
|
// Previous result.
|
||||||
|
assert.deepEqual(actual, [2, 2, 2]);
|
||||||
|
assert.strictEqual(callCount, 1);
|
||||||
|
}, 128);
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
assert.strictEqual(callCount, 2);
|
||||||
|
done();
|
||||||
|
}, 256);
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test('should honor leading: false when maxWait is supplied', function(assert) {
|
||||||
|
assert.expect(6);
|
||||||
|
|
||||||
|
var done = assert.async();
|
||||||
|
|
||||||
|
var callCount = 0;
|
||||||
|
|
||||||
|
var debounced = _.debounce(function(value) {
|
||||||
|
++callCount;
|
||||||
|
return value;
|
||||||
|
}, 32, { 'maxWait': 64 });
|
||||||
|
|
||||||
|
// Leading should not fire.
|
||||||
|
var actual = [debounced(0), debounced(1), debounced(2)];
|
||||||
|
assert.deepEqual(actual, [undefined, undefined, undefined]);
|
||||||
|
assert.strictEqual(callCount, 0);
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
// Trailing should fire by now.
|
||||||
|
assert.strictEqual(callCount, 1);
|
||||||
|
|
||||||
|
// Do it again.
|
||||||
|
var actual = [debounced(4), debounced(5), debounced(6)];
|
||||||
|
|
||||||
|
// Previous result.
|
||||||
|
assert.deepEqual(actual, [2, 2, 2]);
|
||||||
|
assert.strictEqual(callCount, 1);
|
||||||
|
}, 128);
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
assert.strictEqual(callCount, 2);
|
||||||
|
done();
|
||||||
|
}, 256);
|
||||||
|
});
|
||||||
|
|
||||||
QUnit.test('should invoke the `trailing` call with the correct arguments and `this` binding', function(assert) {
|
QUnit.test('should invoke the `trailing` call with the correct arguments and `this` binding', function(assert) {
|
||||||
assert.expect(2);
|
assert.expect(2);
|
||||||
|
|
||||||
@@ -21271,7 +21341,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
QUnit.test('should trigger a second throttled call as soon as possible', function(assert) {
|
QUnit.test('should trigger a second throttled call as soon as possible', function(assert) {
|
||||||
assert.expect(2);
|
assert.expect(3);
|
||||||
|
|
||||||
var done = assert.async();
|
var done = assert.async();
|
||||||
|
|
||||||
@@ -21288,10 +21358,14 @@
|
|||||||
throttled();
|
throttled();
|
||||||
}, 192);
|
}, 192);
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
assert.strictEqual(callCount, 1);
|
||||||
|
}, 254);
|
||||||
|
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
assert.strictEqual(callCount, 2);
|
assert.strictEqual(callCount, 2);
|
||||||
done();
|
done();
|
||||||
}, 288);
|
}, 384);
|
||||||
});
|
});
|
||||||
|
|
||||||
QUnit.test('should apply default options', function(assert) {
|
QUnit.test('should apply default options', function(assert) {
|
||||||
|
|||||||
Reference in New Issue
Block a user