Skip to content
This repository was archived by the owner on Mar 17, 2025. It is now read-only.

Commit 617bf63

Browse files
committed
Merge pull request #486 from jamestalmage/fix-debounce-timing-corner-case
Fix: utils.debounce timing corner case.
2 parents b1de5aa + 17d5e4b commit 617bf63

File tree

2 files changed

+51
-6
lines changed

2 files changed

+51
-6
lines changed

src/utils.js

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
var queue = [];
6363
var start;
6464
var cancelTimer;
65+
var runScheduledForNextTick;
6566

6667
// returns `fn` wrapped in a function that queues up each call event to be
6768
// invoked later inside fo runNow()
@@ -77,14 +78,17 @@
7778
}
7879

7980
// clears the current wait timer and creates a new one
80-
// however, if maxWait is exceeded, calls runNow() immediately
81+
// however, if maxWait is exceeded, calls runNow() on the next tick.
8182
function resetTimer() {
8283
if( cancelTimer ) {
8384
cancelTimer();
8485
cancelTimer = null;
8586
}
8687
if( start && Date.now() - start > maxWait ) {
87-
utils.compile(runNow);
88+
if(!runScheduledForNextTick){
89+
runScheduledForNextTick = true;
90+
utils.compile(runNow);
91+
}
8892
}
8993
else {
9094
if( !start ) { start = Date.now(); }
@@ -96,6 +100,7 @@
96100
function runNow() {
97101
cancelTimer = null;
98102
start = null;
103+
runScheduledForNextTick = false;
99104
var copyList = queue.slice(0);
100105
queue = [];
101106
angular.forEach(copyList, function(parts) {
@@ -114,7 +119,7 @@
114119
* @param {int} [maxWait] max milliseconds to wait before sending out, defaults to wait * 10 or 100
115120
*/
116121
debounce: function(fn, ctx, wait, maxWait) {
117-
var start, cancelTimer, args;
122+
var start, cancelTimer, args, runScheduledForNextTick;
118123
if( typeof(ctx) === 'number' ) {
119124
maxWait = wait;
120125
wait = ctx;
@@ -130,25 +135,29 @@
130135
if( !maxWait ) { maxWait = wait*10 || 100; }
131136

132137
// clears the current wait timer and creates a new one
133-
// however, if maxWait is exceeded, calls runNow() immediately
138+
// however, if maxWait is exceeded, calls runNow() on the next tick.
134139
function resetTimer() {
135140
if( cancelTimer ) {
136141
cancelTimer();
137142
cancelTimer = null;
138143
}
139144
if( start && Date.now() - start > maxWait ) {
140-
utils.compile(runNow);
145+
if(!runScheduledForNextTick){
146+
runScheduledForNextTick = true;
147+
utils.compile(runNow);
148+
}
141149
}
142150
else {
143151
if( !start ) { start = Date.now(); }
144152
cancelTimer = utils.wait(runNow, wait);
145153
}
146154
}
147155

148-
// Clears the queue and invokes all of the functions awaiting notification
156+
// Clears the queue and invokes the debounced function with the most recent arguments
149157
function runNow() {
150158
cancelTimer = null;
151159
start = null;
160+
runScheduledForNextTick = false;
152161
fn.apply(ctx, args);
153162
}
154163

tests/unit/utils.spec.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,42 @@ describe('$firebaseUtils', function () {
5151
});
5252
});
5353

54+
describe('#debounce', function(){
55+
it('should trigger function with arguments',function(){
56+
var spy = jasmine.createSpy();
57+
$utils.debounce(spy,10)('foo', 'bar');
58+
$timeout.flush();
59+
expect(spy).toHaveBeenCalledWith('foo', 'bar');
60+
});
61+
62+
it('should only trigger once, with most recent arguments',function(){
63+
var spy = jasmine.createSpy();
64+
var fn = $utils.debounce(spy,10);
65+
fn('foo', 'bar');
66+
fn('baz', 'biz');
67+
$timeout.flush();
68+
expect(spy.calls.count()).toBe(1);
69+
expect(spy).toHaveBeenCalledWith('baz', 'biz');
70+
});
71+
72+
it('should only trigger once (timing corner case)',function(){
73+
var spy = jasmine.createSpy();
74+
var fn = $utils.debounce(spy, null, 1, 2);
75+
fn('foo', 'bar');
76+
var start = Date.now();
77+
78+
// block for 3ms without releasing
79+
while(Date.now() - start < 3){ }
80+
81+
fn('bar', 'baz');
82+
fn('baz', 'biz');
83+
expect(spy).not.toHaveBeenCalled();
84+
$timeout.flush();
85+
expect(spy.calls.count()).toBe(1);
86+
expect(spy).toHaveBeenCalledWith('baz', 'biz');
87+
});
88+
});
89+
5490
describe('#updateRec', function() {
5591
it('should return true if changes applied', function() {
5692
var rec = {};

0 commit comments

Comments
 (0)