Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add Function.debounce #2720

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
});

});