Skip to content

Commit

Permalink
add Function.debounce
Browse files Browse the repository at this point in the history
  • Loading branch information
SergioCrisostomo committed Nov 10, 2015
1 parent 61efb25 commit 13a2199
Show file tree
Hide file tree
Showing 3 changed files with 190 additions and 0 deletions.
28 changes: 28 additions & 0 deletions Docs/Types/Function.md
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,34 @@ Executes a function in the specified intervals of time. Periodic execution can b

- [MDN setInterval][], [MDN clearInterval][]

Function: Function.debounce {#Function:Function-debounce}
---------------------------------------------------------

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.

### Syntax:

var debounceFn = myFn.debounce(delay, leading);

### Arguments:

1. delay - (*number*, optional, defaults to 250ms) The delay to wait before a call to the debounced function can happen again.
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.

### Returns:

* (*function*) A debounce function that will be called only once per group of close function calls.

### Examples:

// get scroll position after scroll has stopped
var getNewScrollPosition = function () {
var scroll = window.getScroll();
alert(scroll.y);
}
window.addEvent('scroll', getNewScrollPosition.debounce(500));



Deprecated Functions {#Deprecated-Functions}
============================================
Expand Down
29 changes: 29 additions & 0 deletions Source/Types/Function.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,35 @@ Function.implement({

periodical: function(periodical, bind, args){
return setInterval(this.pass((args == null ? [] : args), bind), periodical);
},

debounce: function(delay, leading){

// in case delay is omitted and `leading` is first argument
if (typeof delay == 'boolean'){
leading = delay;
delay = false;
}

var timeout, args, self,
fn = this,
callNow = leading;

var later = function(){
if (leading) callNow = true;
else fn.apply(self, args);
timeout = null;
};

return function(){
self = this;
args = arguments;

clearTimeout(timeout);
timeout = setTimeout(later, delay || 250);
if (callNow) fn.apply(self, args);
callNow = false;
};
}

});
Expand Down
133 changes: 133 additions & 0 deletions Specs/Types/Function.js
Original file line number Diff line number Diff line change
Expand Up @@ -460,3 +460,136 @@ describe('Function.periodical', function(){
});

});

describe('Debounce', function(){
var periodical,
counter = 0,
debounceCalls = 0;

// spy, to count when original fn was called
function targetFn(){
debounceCalls++;
}

// call function every 10ms
function caller(debounceFn, cb){
periodical = setInterval(function(){
counter++;
debounceFn();
}, 10);
}

beforeEach(function(){
expect(counter).to.equal(0);
expect(debounceCalls).to.equal(0);
});

afterEach(function(){
counter = debounceCalls = 0;
clearInterval(periodical);
});

it('should debounce with default values', function(done){
var debounceFn = targetFn.debounce();
caller(debounceFn);

var firstCheck = false;
var wait = setInterval(function(){
// keep calling for 400ms, no call should be done
if (!firstCheck && counter > 40){
clearInterval(periodical);
expect(debounceCalls).to.equal(0);
firstCheck = true;
}
// wait for debouced call to come
if (firstCheck && debounceCalls > 0){
clearInterval(wait);
done();
}
}, 10);
});

it('should debounce early', function(done){
var debounceFn = targetFn.debounce(100, true),
time = 0,
firstCheck;
caller(debounceFn);
var wait = setInterval(function(){
time++;
// there should already be a function called
if (counter > 5 && !firstCheck){
clearInterval(periodical);
expect(debounceCalls).to.equal(1);
firstCheck = true;
}
// no more debounced call should be done
if (time > 40){
expect(debounceCalls).to.equal(1);
clearInterval(wait);
done();
}
}, 10);
});

it('should debounce early when `leading` is passed alone', function(done){
var debounceFn = targetFn.debounce(true),
time = 0,
firstCheck;
caller(debounceFn);
var wait = setInterval(function(){
time++;
// there should already be a function called
if (counter > 5 && !firstCheck){
clearInterval(periodical);
expect(debounceCalls).to.equal(1);
firstCheck = true;
}
// no more debounced call should be done
if (time > 40){
expect(debounceCalls).to.equal(1);
clearInterval(wait);
done();
}
}, 10);
});

it('should debounce late', function(done){
var debounceFn = targetFn.debounce(150);
caller(debounceFn);
var time = 0;
var firstCheck;
var wait = setInterval(function(){
time++;
// no early calls
if (counter > 5 && !firstCheck){
clearInterval(periodical);
expect(debounceCalls).to.equal(0);
firstCheck = true;
}
// should have been called after
if (time > 30){
expect(debounceCalls).to.equal(1);
clearInterval(wait);
done();
}
}, 10);
});

it('should have the right context', function(done){
var context = {};
var _context;
var fn = function(){
_context = this;
}.bind(context);
var debounceFn = fn.debounce();
debounceFn(); // trigger the function, once is enough
var wait = setInterval(function(){
if (_context){
clearInterval(wait);
expect(_context).to.equal(context);
done();
}
}, 10);
});

});

0 comments on commit 13a2199

Please sign in to comment.