Skip to content

Commit 13a2199

Browse files
add Function.debounce
1 parent 61efb25 commit 13a2199

File tree

3 files changed

+190
-0
lines changed

3 files changed

+190
-0
lines changed

Docs/Types/Function.md

+28
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,34 @@ Executes a function in the specified intervals of time. Periodic execution can b
368368

369369
- [MDN setInterval][], [MDN clearInterval][]
370370

371+
Function: Function.debounce {#Function:Function-debounce}
372+
---------------------------------------------------------
373+
374+
This method will return a new function that will be called only once per group of close calls. After a defined delay it will be able to be called again.
375+
376+
### Syntax:
377+
378+
var debounceFn = myFn.debounce(delay, leading);
379+
380+
### Arguments:
381+
382+
1. delay - (*number*, optional, defaults to 250ms) The delay to wait before a call to the debounced function can happen again.
383+
2. leading - (*boolean*, optional, defaults to false) If the call to the debounced function should happen in leading phase of group of calls or after.
384+
385+
### Returns:
386+
387+
* (*function*) A debounce function that will be called only once per group of close function calls.
388+
389+
### Examples:
390+
391+
// get scroll position after scroll has stopped
392+
var getNewScrollPosition = function () {
393+
var scroll = window.getScroll();
394+
alert(scroll.y);
395+
}
396+
window.addEvent('scroll', getNewScrollPosition.debounce(500));
397+
398+
371399

372400
Deprecated Functions {#Deprecated-Functions}
373401
============================================

Source/Types/Function.js

+29
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,35 @@ Function.implement({
7272

7373
periodical: function(periodical, bind, args){
7474
return setInterval(this.pass((args == null ? [] : args), bind), periodical);
75+
},
76+
77+
debounce: function(delay, leading){
78+
79+
// in case delay is omitted and `leading` is first argument
80+
if (typeof delay == 'boolean'){
81+
leading = delay;
82+
delay = false;
83+
}
84+
85+
var timeout, args, self,
86+
fn = this,
87+
callNow = leading;
88+
89+
var later = function(){
90+
if (leading) callNow = true;
91+
else fn.apply(self, args);
92+
timeout = null;
93+
};
94+
95+
return function(){
96+
self = this;
97+
args = arguments;
98+
99+
clearTimeout(timeout);
100+
timeout = setTimeout(later, delay || 250);
101+
if (callNow) fn.apply(self, args);
102+
callNow = false;
103+
};
75104
}
76105

77106
});

Specs/Types/Function.js

+133
Original file line numberDiff line numberDiff line change
@@ -460,3 +460,136 @@ describe('Function.periodical', function(){
460460
});
461461

462462
});
463+
464+
describe('Debounce', function(){
465+
var periodical,
466+
counter = 0,
467+
debounceCalls = 0;
468+
469+
// spy, to count when original fn was called
470+
function targetFn(){
471+
debounceCalls++;
472+
}
473+
474+
// call function every 10ms
475+
function caller(debounceFn, cb){
476+
periodical = setInterval(function(){
477+
counter++;
478+
debounceFn();
479+
}, 10);
480+
}
481+
482+
beforeEach(function(){
483+
expect(counter).to.equal(0);
484+
expect(debounceCalls).to.equal(0);
485+
});
486+
487+
afterEach(function(){
488+
counter = debounceCalls = 0;
489+
clearInterval(periodical);
490+
});
491+
492+
it('should debounce with default values', function(done){
493+
var debounceFn = targetFn.debounce();
494+
caller(debounceFn);
495+
496+
var firstCheck = false;
497+
var wait = setInterval(function(){
498+
// keep calling for 400ms, no call should be done
499+
if (!firstCheck && counter > 40){
500+
clearInterval(periodical);
501+
expect(debounceCalls).to.equal(0);
502+
firstCheck = true;
503+
}
504+
// wait for debouced call to come
505+
if (firstCheck && debounceCalls > 0){
506+
clearInterval(wait);
507+
done();
508+
}
509+
}, 10);
510+
});
511+
512+
it('should debounce early', function(done){
513+
var debounceFn = targetFn.debounce(100, true),
514+
time = 0,
515+
firstCheck;
516+
caller(debounceFn);
517+
var wait = setInterval(function(){
518+
time++;
519+
// there should already be a function called
520+
if (counter > 5 && !firstCheck){
521+
clearInterval(periodical);
522+
expect(debounceCalls).to.equal(1);
523+
firstCheck = true;
524+
}
525+
// no more debounced call should be done
526+
if (time > 40){
527+
expect(debounceCalls).to.equal(1);
528+
clearInterval(wait);
529+
done();
530+
}
531+
}, 10);
532+
});
533+
534+
it('should debounce early when `leading` is passed alone', function(done){
535+
var debounceFn = targetFn.debounce(true),
536+
time = 0,
537+
firstCheck;
538+
caller(debounceFn);
539+
var wait = setInterval(function(){
540+
time++;
541+
// there should already be a function called
542+
if (counter > 5 && !firstCheck){
543+
clearInterval(periodical);
544+
expect(debounceCalls).to.equal(1);
545+
firstCheck = true;
546+
}
547+
// no more debounced call should be done
548+
if (time > 40){
549+
expect(debounceCalls).to.equal(1);
550+
clearInterval(wait);
551+
done();
552+
}
553+
}, 10);
554+
});
555+
556+
it('should debounce late', function(done){
557+
var debounceFn = targetFn.debounce(150);
558+
caller(debounceFn);
559+
var time = 0;
560+
var firstCheck;
561+
var wait = setInterval(function(){
562+
time++;
563+
// no early calls
564+
if (counter > 5 && !firstCheck){
565+
clearInterval(periodical);
566+
expect(debounceCalls).to.equal(0);
567+
firstCheck = true;
568+
}
569+
// should have been called after
570+
if (time > 30){
571+
expect(debounceCalls).to.equal(1);
572+
clearInterval(wait);
573+
done();
574+
}
575+
}, 10);
576+
});
577+
578+
it('should have the right context', function(done){
579+
var context = {};
580+
var _context;
581+
var fn = function(){
582+
_context = this;
583+
}.bind(context);
584+
var debounceFn = fn.debounce();
585+
debounceFn(); // trigger the function, once is enough
586+
var wait = setInterval(function(){
587+
if (_context){
588+
clearInterval(wait);
589+
expect(_context).to.equal(context);
590+
done();
591+
}
592+
}, 10);
593+
});
594+
595+
});

0 commit comments

Comments
 (0)