Remy Sharp's function throttler - javascript

trying to use a throttling function created by Remy Sharp (http://remysharp.com/2010/07/21/throttling-function-calls/)... it works in the standard use like so:
$('.thing').click(throttle(function() {
console.log('api call');
}, 300);
Which is pretty neat, but I wanted to be able to throttle a specific part of code within a function, not on the entire click event, like so:
$('.thing').click(function() {
console.log('hello!');
throttle(function() {
console.log('api call');
}, 300);
});
But I don't quite understand why it doesn't work. The throttle code returns a function so if I proceed the throttle call with .apply(this, arguments); then type 'hello', the function is called 5 times rather than once as the timer within the throttling function isn't being overwritten.
Sifted through the jQuery click code but couldn't really find anything. I'm guessing jQuery creates one instance of it and then recalls the same instance so the same timer is there?
Does anyone understand this and why it happens like so?

You're doing it wrong ;-)
Here's the solution on jsbin: http://jsbin.com/elabul/edit
The throttle method returns a function that will throttle the number of times it's called. So you need to capture that bad boy in a variable and call it from inside your click handler, as such:
var inputThrottle = throttle(function () {
console.log('api call');
}, 250);
$('input').keyup(function () {
console.log('test');
inputThrottle();
});

you need to call returned from throttle function:
$('.thing').click(function() {
console.log('hello!');
(throttle(function() {
console.log('api call');
}, 500))();
});

Just calling throttle does nothing, you need to return the function as a function to the click handler:
$('.thing').click((function() {
console.log('hello!');
return throttle(function() {
console.log('api call');
}, 300);
}()));

Related

Debounce function in Jquery?

Been looking for a debounce function or way to debounce in Jquery. The build up of animations can get super annoying.
Heres the code:
function fade() {
$('.media').hide();
$('.media').fadeIn(2000);
}
var debounce = false;
function colorChange() {
if (debounce) return;
debounce = true;
$('.centered').mouseenter(function() {
$('.centered').fadeTo('fast', .25);
});
$('.centered').mouseleave(function() {
$('.centered').fadeTo('fast', 1);
});
}
function colorChange2() {
$('.centered2').mouseenter(function() {
$('.centered2').fadeTo('fast', .25);
});
$('.centered2').mouseleave(function() {
$('.centered2').fadeTo('fast', 1);
});
}
function colorChange3() {
$('.centered3').mouseenter(function() {
$('.centered3').fadeTo('fast', .25);
});
$('.centered3').mouseleave(function() {
$('.centered3').fadeTo('fast', 1);
});
}
function closerFade() {
$('.closer').hide();
$('.closer').fadeIn(2000);
}
I wrapped those all in $(document).ready(function() {
Is there way to debounce??
I donĀ“t like the idea to include a library just for a debounce function. You can just do:
var debounce = null;
$('#input').on('keyup', function(e){
clearTimeout(debounce );
debounce = setTimeout(function(){
$.ajax({url: 'someurl.jsp', data: {query: q}, type: 'GET'})
}, 100);
});
I would just include underscore.js in my project and use the debounce function that it contains. It works great. I've used it in multiple projects.
http://underscorejs.org/#debounce
debounce_.debounce(function, wait, [immediate]) Creates and returns a
new debounced version of the passed function which will postpone its
execution until after wait milliseconds have elapsed since the last
time it was invoked. Useful for implementing behavior that should only
happen after the input has stopped arriving. For example: rendering a
preview of a Markdown comment, recalculating a layout after the window
has stopped being resized, and so on.
At the end of the wait interval, the function will be called with the
arguments that were passed most recently to the debounced function.
Pass true for the immediate argument to cause debounce to trigger the
function on the leading instead of the trailing edge of the wait
interval. Useful in circumstances like preventing accidental
double-clicks on a "submit" button from firing a second time.
var lazyLayout = _.debounce(calculateLayout, 300);
$(window).resize(lazyLayout);

lodash debounce not working in anonymous function

Hello I cannot seem to figure out why the debounce function works as expected when passed directly to a keyup event; but it does not work if I wrap it inside an anonymous function.
I have fiddle of the problem: http://jsfiddle.net/6hg95/1/
EDIT: Added all the things I tried.
HTML
<input id='anonFunction'/>
<input id='noReturnAnonFunction'/>
<input id='exeDebouncedFunc'/>
<input id='function'/>
<div id='output'></div>
JAVASCRIPT
$(document).ready(function(){
$('#anonFunction').on('keyup', function () {
return _.debounce(debounceIt, 500, false); //Why does this differ from #function
});
$('#noReturnAnonFunction').on('keyup', function () {
_.debounce(debounceIt, 500, false); //Not being executed
});
$('#exeDebouncedFunc').on('keyup', function () {
_.debounce(debounceIt, 500, false)(); //Executing the debounced function results in wrong behaviour
});
$('#function').on('keyup', _.debounce(debounceIt, 500, false)); //This is working.
});
function debounceIt(){
$('#output').append('debounced');
}
anonFunction and noReturnAnonFunction does not fire the debounce function; but the last function does fire. I do not understand why this is. Can anybody please help me understand this?
EDIT
Ok, so the reason that the debounce does not happen in #exeDebouncedFunc (the one you refer) is because the function is executed in the scope of the anonymous function and another keyup event will create a new function in another anonymous scope; thus firing the debounced function as many times as you type something (instead of firing once which would be the expected behaviour; see beviour of #function)?
Can you please explain the difference between #anonFunction and the #function. Is this again a matter of scoping why one of them works and the other does not?
EDIT
Ok, so now I understand why this is happening. And here is why I needed to wrap it inside an anonymous function:
Fiddle: http://jsfiddle.net/6hg95/5/
HTML
<input id='anonFunction'/>
<div id='output'></div>
JAVASCRIPT
(function(){
var debounce = _.debounce(fireServerEvent, 500, false);
$('#anonFunction').on('keyup', function () {
//clear textfield
$('#output').append('clearNotifications<br/>');
debounce();
});
function fireServerEvent(){
$('#output').append('serverEvent<br/>');
}
})();
As Palpatim explained, the reason lies in the fact that _.debounce(...) returns a function, which when invoked does its magic.
Therefore in your #anonFunction example, you have a key listener, which when invoked does nothing but return a function to the invoker, which does nothing with the return values from the event listener.
This is a snippet of the _.debounce(...) definition:
_.debounce
function (func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
if (immediate && !timeout) func.apply(context, args);
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
Your key event listener must invoke the returned function from _.debounce(...), or you can do as in your non-anonymous example and use the returned function from the _.debounce(...) call as your event listener.
Think easier
_.debounce returns a debounced function!
So instead of thinking in terms of
$el.on('keyup'), function(){
_.debounce(doYourThing,500); //uh I want to debounce this
}
you rather call the debounced function instead
var doYourThingDebounced = _.debounce(doYourThing, 500); //YES, this will always be debounced
$el.on('keyup', doYourThingDebounced);
debounce doesn't execute the function, it returns a function with the debounciness built into it.
Returns
(Function): Returns the new debounced function.
So your #function handler is actually doing the Right Thing, by returning a function to be used by jQuery as a keyup handler. To fix your #noReturnAnonFunction example, you could simply execute the debounced function in the context of your function:
$('#noReturnAnonFunction').on('keyup', function () {
_.debounce(debounceIt, 500, false)(); // Immediately executes
});
But that's introducing a needless anonymous function wrapper around your debounce.
You can return the debounce function like this:
(function(){
var debounce = _.debounce(fireServerEvent, 500, false);
$('#anonFunction').on('keyup', function () {
//clear textfield
$('#output').append('clearNotifications<br/>');
return debounce();
});
function fireServerEvent(){
$('#output').append('serverEvent<br/>');
}
})();
Came across this while looking for a solution to calling a debounce with a trailing call, found this article which really helped me:
https://newbedev.com/lodash-debounce-not-working-in-react
specifically:
Solution for those who came here because throttle / debounce doesn't work >with FunctionComponent - you need to store debounced function via useRef():
export const ComponentName = (value = null) => {
const [inputValue, setInputValue] = useState(value);
const setServicesValue = value => Services.setValue(value);
const setServicesValueDebounced = useRef(_.debounce(setServicesValue, 1000));
const handleChange = ({ currentTarget: { value } }) => {
setInputValue(value);
setServicesValueDebounced.current(value);
};
return <input onChange={handleChange} value={inputValue} />;
};
More generally, if you want a debounce with a trailing behaviour (accounts for last click, or more likely last change on a select input), and a visual feedback on first click/change, you are faced with the same issue.
This does not work:
$(document).on('change', "#select", function() {
$('.ajax-loader').show();
_.debounce(processSelectChange, 1000);
});
This would be a solution:
$(document).on('change', "#select", function() {
$('.ajax-loader').show();
});
$(document).on('change', "#select", _.debounce(processSelectChange, 1000));

How to spy on a function that is expected to be asynchronous?

I have this function that I expect to be invoked after 5s of invoking the jasmine test script.
I tried the traditional way described in the jasmine docs:
describe("Tests:", function(){
it("Expects slowFunction() will be called", function(){
var slowFunctionSpy = spyOn(window, 'slowFunction').andCallThrough();
init();
waitsFor(function() {
expect(slowFunctionSpy).toHaveBeenCalled();
}, "Call Not Answered by Remote End.", 10000);
});
});
the init() function fires a chain of events, that is it inoves a function which ultimately brings it to slowFunction() being invoked.
I have left out the run() method because I think if the waitsFor() gets the expect() to be true the test should pass right?
I have also tried jasmine.async but the test still fails.
describe("Tests:", function(){
var foo=false;
var async = new AsyncSpec(this);
async.beforeEach(function(done){
setTimeout(function(done){
foo = true;
init();
done();
}, 10000);
});
it("Expects slowFunction() will be called", function(){
var slowFunctionSpy = spyOn(window, 'slowFunction').andCallThrough();
expect(slowFunctionSpy).toHaveBeenCalled();
});
});
Probably theres something I do not clearly understand about. Some help in clearing out my ideas please?
UPDATE: slowFunction() is a custom event and based on my google search outcome, I think spyOn() doesnt work for events. So new question:
How to spy on a custom event in Jasmine?

Javascript this object and bind

new Something({
events:{
load: (function loopsiloop(){
console.log(this); // firstime this will be Something, but next call what its
setTimeout(function(){
console.log(this);
$.ajax({
url: 'foo.htm',
context: this,
success: function( response ){
// do something with the response
},
error: function(){
// do some error handling. you
// should probably adjust the timeout
// here.
},
complete: function(){
loopsiloop(); // recurse //need to bind this here like loopsiloop().bind(this)
}
});
}.bind(this), 5000);
}),
click: function(){
alert("clicked");
}
}
})
Please go through the code and read comments, here the problem is i need to use this in setTimeOut function, so I am binding this to setTimeOut, but when I am calling function as recursive the value of this will not be same
NB:- I dont want to pass the object to the function and dont want to use setIntervel (http://www.erichynds.com/javascript/a-recursive-settimeout-pattern/)
Your recursive call can be written like this:
complete: function() {
loopsiloop.call(this);
}
to ensure that the context is correctly set the second time around.
It could also be written like this, but it's not recommended as it'll call .bind over and over on each pass:
complete: loopsiloop().bind(this) // NB: no function wrap, just passes the ref
Don't bind and don't use this. Set var someVariable = this; before you call setTimeout and let it remain in scope for the recursion (using it instead of this inside the function).

How to delay a function from returning until after clicks have occurred

The following confirmDialog function is called midway through another jquery function. When this confirmDialog returns true the other function is supposed to continue... but it doesn't. The reason for this seems to be that the entire confirmDialog function has already executed (returning false) by the time the continue button gets clicked. How can I delay it returning anything until after the buttons have been clicked?
(Or, if I'm completely on the wrong track, what is the problem and how do I fix it?)
function confirmDialog(message) {
....
$('input#continue', conf_dialog).click(function() {
$(this).unbind();
$('p',conf_dialog).fadeOut().text('Are you really sure you want to '+message).fadeIn();
$(this).click(function() {
$(conf_dialog).remove();
return true;
});
});
$('input#cancel', conf_dialog).click(function() {
$(conf_dialog).remove();
return false;
});
}
Im' not sure you can.
AFAIK only built-in function like confirm, alert or prompt can be blocking while asking for an answer.
The general workaround is to refactor your code to use callbacks (or use the built-in functions). So that would mean splitting your caller function in two, and executing the second part when the input is obtained.
In confirmDialog, you're setting up event handlers, that will execute when events are fired, not when confirmDialog is run. Another issue, is that you return true or false inside the event function, so that won't apply to the outer function confirmDialong.
The part that relies on the button presses would need to be re-factored. Perhaps put it in another function, and call it from the click handlers:
var afterConfirm = function(bool) {
if(bool) {
//continue clicked
} else {
//cancel clicked
}
//do for both cases here
}
//inside confirmDialog input#continue
$(this).click(function() {
$(conf_dialog).remove();
afterConfirm(true);
});
You may want to look into using Deferred objects. Here are two links that explain them.
http://www.sitepen.com/blog/2009/03/31/queued-demystifying-deferreds/
http://api.dojotoolkit.org/jsdoc/1.3/dojo.Deferred
Using a Deferred you could take your calling function:
function doSomething () {
// this functions does something before calling confirmDialog
if (confirmDialog) {
// handle ok
} else {
// handle cancel
}
// just to be difficult lets have more code here
}
and refactor it to something like this:
function doSomethingRefactored() {
// this functions does something before calling confirmDialog
var handleCancel = function() { /* handle cancel */};
var handleOk = function() { /* handle ok */};
var doAfter = function() { /* just to be difficult lets have more code here */};
var d = new dojo.deferred();
d.addBoth(handleOk, handleCancel);
d.addCallback(doAfter);
confirmDialog(message, d);
return d;
}
ConfirmDialog would have to be
updated to call d.callback() or
d.errback() instead of returning true
or false
if the function that calls
doSomething needs to wait for
doSomething to finish it can add its
own functions to the callback chain
Hope this helps... it will make a lot more sense after reading the sitepen article.
function callingFunction() {
$('a').click(function() {
confirmDialog('are you sure?', dialogConfirmed);
// the rest of the function is in dialogConfirmed so doesnt
// get run unless the confirm button is pressed
})
}
function dialogConfirmed() {
// put the rest of your function here
}
function confirmDialog(message, callback) {
...
$('input#continue', conf_dialog).click(function() {
callback()
$(conf_dialog).remove();
return false;
}),
$('input#cancel', conf_dialog).click(function() {
$(conf_dialog).remove();
return false;
})
...
}
You could add a timeout before the next function is called
http://www.w3schools.com/htmldom/met_win_settimeout.asp

Categories