I am trying to set up an image swapper function. Here is my code so far:
var imageChanger = function(start, end) {
var start = 1;
var end = 22;
return {
count: function(url) {
var self = this;
if(start > end) {
start = 1;
}
console.log(url);
console.log(start++);
imageSwapper = setTimeout( function() {
self.count();
}, 2000)
},
stopCount: function() {
clearTimeout(imageSwapper);
}
}
}
As you can see, this is a function that takes two parameters. It then returns an object of it's own with two methods. When I call the count method after the initial imageChanger function call and pass a parameter to url it only logs what I pass one time and then when the setTimeout function runs, undefined subsequent times.
I am not sure what I am doing wrong here. Why is this count function returning undefined after the first log??
In the setTimeout, you should call self.count with the url argument instead of no-argument, i.e.
self.count(url);
Related
The documentation for the _.throttle function states that:
Creates a throttled function that only invokes func at most once per
every wait milliseconds. The throttled function comes with a cancel
method to cancel delayed func invocations and a flush method to
immediately invoke them. Provide an options object to indicate whether
func should be invoked on the leading and/or trailing edge of the wait
timeout. The func is invoked with the last arguments provided to the
throttled function. Subsequent calls to the throttled function return
the result of the last func invocation
I'm interested in this line:
Subsequent calls to the throttled function return
the result of the last func invocation
I've tried:
var throttled = _.throttle(updateModelData, 1000);
service.on('change', function () {
throttled(5);
});
function updateModelData(data) {
// all calls here log 5's
console.log(data);
return data;
}
setTimeout(function() {
throttled(); // here updateModelData is executed with `undefined` value
}, 5000);
The problem is that throttled() triggers function without returning the data. How can I invoke it so that it returns last data?
EDIT:
According to source code, the value will be returned only if no pending function call exists isCalled === false:
function debounced() {
args = arguments;
stamp = now();
thisArg = this;
trailingCall = trailing && (timeoutId || !leading);
if (maxWait === false) {
var leadingCall = leading && !timeoutId;
} else {
if (!maxTimeoutId && !leading) {
lastCalled = stamp;
}
var remaining = maxWait - (stamp - lastCalled),
isCalled = remaining <= 0 || remaining > maxWait;
!!!!! HERE
if (isCalled) {
if (maxTimeoutId) {
maxTimeoutId = clearTimeout(maxTimeoutId);
}
lastCalled = stamp;
result = func.apply(thisArg, args);
}
else if (!maxTimeoutId) {
maxTimeoutId = setTimeout(maxDelayed, remaining);
}
}
...
return result;
}
So the following will work:
var throttled = _.throttle(updateModelData, 10000);
service.on('change', function () {
throttled(5);
});
function updateModelData(data) {
// all calls here log 5's
console.log(data);
return data;
}
setTimeout(function() {
throttled(); // returns 5
}, 15000);
The issue is that, when you have leading invocations (the default behavior for _.throttle), when you first call the throttled function (or first call it after after your delay time has passed) it immediately calls the underlying function, before returning anything.
That means that the "result of the last function invocation" might be the result of a function invocation that was caused by your current call to the throttled function. So your call to throttle() calls updateModelData() and then returns undefined, since updateModelData() returns undefined.
Here's some sample code that might clarify this:
var foo = (x) => x;
var leading = _.throttle(foo, DELAY, {leading: true, trailing: false}); //these are the default options for leading and trailing
var trailing = _.throttle(foo, DELAY, {leading: false, trailing: true});
leading(1); //Calls foo(1), returns 1
leading(2); //Doesn't call foo, returns 1,
leading(3); //Doesn't call foo, returns 1
trailing(1); //Doesn't call foo, returns undefined
trailing(2); //Doesn't call foo, returns undefined
//DELAY ms later
//foo(2) is called, due to the most recent call to bar2
leading(); //Calls foo(), returns undefined
leading(1); //Still returns undefined from above
trailing(); //Doesn't call foo, returns 2
trailing(1); //Doesn't call foo, returns 2
//Another DELAY ms later
leading("Whatever"); //Calls foo("Whatever"), returns "Whatever";
Here's a version of your JSFiddle that makes it slightly more obvious too.
Really, you shouldn't call a function just to get the last value returned by it, so I'd suggest you just manage the last value yourself and not rely on _.throttle to do it for you. For example:
var lastResultOfFoo;
var foo = function (x) {
lastResultOfFoo = x;
return x;
}
//OR (if you don't mind attaching arbitrary properties to functions)
var foo = function (x) {
foo.lastResult = x;
return x;
}
The following code works fine:
var throttled = _.throttle(updateModelData, 1000);
var i = 0;
function updateModelData(data) {
return data;
}
var interval = setInterval(function() {
console.log(throttled(i++));
if (i === 6) {
clearInterval(interval);
console.log('Last value: ' + throttled());
}
}, 2000);
Output:
0
1
2
3
4
5
"Last value: 5"
DEMO
This is code used within nodered.
I'm invoking several timers with the same function, then either the timer actually runs and displays something,
or I stop the timer (clearTimeout) and the something doens't get displayed.
The first thing I tried is this:
// multiple calls method 1 - DOES NOT WORK (multiple calls of procedure with same name - no method to distuinguish
function displaysomethingelse7 (rdelay7, var37, var47) {
function performactualstuff (var3a7, var4a7) {
node.warn ("37:"+var3a7+", 47:"+var4a7);
}
timer7=setTimeout(performactualstuff, rdelay7, var37, var47);
node.warn ("starting timer27_inprocedure: "+timer7._idleStart);
function stop7() {
if (timer7) {
clearTimeout(timer7);
node.warn ("stopping timerid27 "+timer7._idleStart);
timer7 = 0;
}
}
return stop7;
}
// start 1
delay20=8500;
var20a=2;
var20b="b";
var t10 = displaysomethingelse7 (delay20, var20a, var20b);
// start 2
delay21=10500;
var21a=3;
var21b="c";
var t11 = displaysomethingelse7 (delay21, var21a, var21b);
// stop 1 ?
stopdelay30=8000;
setTimeout(t10, stopdelay30);
// stop 2 ?
stopdelay31=9000;
setTimeout(t11, stopdelay31);
This doens't work since the 'stop7' function has no method to disguinguish between timerIDs.
So I came up with an array of functions:
// multiple calls method 2 - array of functions
function displaysomethingelsetoo (r2delay, var77, var88) {
function performactualstufftoo (var77a, var88a) {
node.warn ("77:"+var77a+", 88:"+var88a);
}
timer8=setTimeout(performactualstufftoo, r2delay, var77, var88);
node.warn ("starting timer77_inprocedure= "+timer8._idleStart);
if (typeof stopa === 'undefined') stopa=[];
stopa[timer8._idleStart] = function (tf) {
if (tf) {
clearTimeout(tf);
node.warn ("stopping timerid3 "+tf._idleStart+"originaltimer="+timer8._idleStart);
tf = 0;
}
}
return stopa[timer8._idleStart];
}
// start 1
delay3=4000;
var5a=4;
var6a="d";
var t3a = displaysomethingelsetoo (delay3, var5a, var6a);
// start 2
delay4=5000;
var5b=5;
var6b="e";
var t3b = displaysomethingelsetoo (delay4, var5b, var6b);
// stop 1 ?
stopdelay3=2000;
setTimeout(t3a, stopdelay3, t3a);
// stop 2 ?
stopdelay4=3000;
setTimeout(t3b, stopdelay4, t3b);
But this isn't quite correct yet either - the stopa array has all the same function in it.
I think the solution could be to pass the parsed timer8 variable to the stopa[timer8._idleStart] function,
but I have no idea how to to do this.
This doens't work since the 'stop7' function has no method to disguinguish between timerIDs
You will want to use a closure here. I think you already tried to use one, and your code is structured like you were using one, there's only a tiny modification necessary: declare the variable as local to the displaysomethingelse7 function so that each invocation will create a new variable.
function displaysomethingelse(rdelay, a, b) {
function performactualstuff() {
node.warn ("37:"+a+", 47:"+b);
// btw, you'll want to close over a and b here as well
}
var timer = setTimeout(performactualstuff, rdelay);
// ^^^
node.warn ("starting timer_inprocedure: "+timer._idleStart);
return function stop() {
if (timer) {
clearTimeout(timer);
node.warn ("stopping timer "+timer._idleStart);
timer = 0;
}
};
}
I am trying to create a recursive function in Javascript. But in order to loop my XML file properly I am trying to pass the right value taken from the XML length and pass it to the setTimeout function.
The problem is that the setTimeout ( setTimeout('cvdXmlBubbleStart(nextIndex)', 3000);
)function does not get the value of nextIndex and thinks it is undefined. I am sure I am doing something wrong.
jQuery(document).ready(function($) {
cvdXmlBubbleStart('0');
});
function cvdXmlBubbleStart(nextIndex) {
$.ajax({
url: "cross_video_day/xml/broadcasted.xml",
dataType: "xml",
cache: false,
success: function(d) {
broadcastedXML = d;
cvdBubbleXmlProcess(nextIndex);
}
});
}
function cvdBubbleXmlProcess(nextIndex) {
var d = broadcastedXML;
//console.log(nextIndex);
var length = $(d).find('tweet').length;
if((nextIndex + 1) < length) {
nextIndex = length - 1;
$(d).find('tweet').eq(nextIndex).each(function(idx) {
var cvdIndexId = $(this).find("index");
var cvdTweetAuthor = $(this).find("author").text();
var cvdTweetDescription = $(this).find("description").text();
if (cvdTweetAuthor === "Animator") {
$('#cvd_bubble_left').html('');
obj = $('#cvd_bubble_left').append(makeCvdBubbleAnimator(cvdIndexId, cvdTweetAuthor, cvdTweetDescription));
obj.fitText(7.4);
$('#cvd_bubble_right').html('');
setTimeout('$(\'#cvd_bubble_left\').html(\'\')', 3000);
} else {
$('#cvd_bubble_right').html('');
obj = $('#cvd_bubble_right').append(makeCvdBubble(cvdIndexId, cvdTweetAuthor, cvdTweetDescription));
obj.fitText(7.4);
$('#cvd_bubble_left').html('');
setTimeout('$(\'#cvd_bubble_right\').html(\'\')', 3000);
}
});
}else{
$('#cvd_bubble_left').html('');
$('#cvd_bubble_right').html('');
}
//broadcastedXMLIndex++;
setTimeout('cvdXmlBubbleStart(nextIndex)', 3000);
}
Using an anonymous function will work because it shares the same scope as nextIndex.
setTimeout(function(){cvdXmlBubbleStart(nextIndex);}, 3000);
The reason that your current code does not work for you is because when you use a string inside of the setTimeout function it uses the Function constructor to create a function based on the string passed (which is similar to using eval and is not best practice). What is worse here is that the function created with Function will not share the same scope as where it was created and thus not have access to nextIndex.
Checkout How can I pass a parameter to a setTimeout() callback? - basically you need to pass an anonymous function to the set timeout call
setTimeout(function(){
cvdXmlBubbleStart(nextIndex)
}, 3000);
function updateServerList() {
var i;
for (i=0; i < servers.length; i++) {
var server = servers[i];
var ip = server['serverIp']
var html = constructServer(i);
var divId = '#server' + ip.replace(new RegExp("\\.", "mg"), "-");
var visible = $(divId).find(".server_body").is(":visible");
var div = $(divId);
div.html(html);
// Set div class.
var prevState = div.attr('class').substring(7)
if (prevState != server['state']) {
if (server['state'] == 'ok') {
console.debug(server);
div.slideUp('fast', function(server) {
$(this).removeClass();
$(this).addClass('server_ok');
var id = ipToId[server['serverIp']];
console.debug(id);
if (id == 0) {
adjacentIp = servers[1]['serverIp'];
adjacentDivId = '#server' + adjacentIp.replace(new RegExp('\\.', 'g'), '-');
$(adjacentDivId).before(this);
}
}).delay(1000);
div.slideDown();
}
}
}
console.debug shows server as being defined, but inside the anonymous function, server is not defined. What am I going wrong?
because server is an argument to the function, its masking the value of the server at the higher level. You need to either pass server to the function, or remove the function argument. I would do the latter, as slideUp doesn't give you a way to pass arguments. You could do it but its needlessly complicated; it would look something like the following
div.slideUp('fast', (function(server) {
return function(){
// your stuff here, server is now 'closed in', i.e. in a closure
}
})(server)); // <-- this server is the current value in the loop
what you are doing here is invoking a new function right away, passing in the argument server, and returning a new function that receives that value.
var server = servers[i];
var prevState = div.attr('class').substring(7);
if (prevState != server['state']) {
if (server['state'] == 'ok') {
console.debug(server);
div.slideUp('fast', function() {
...
var id = ipToId[server['serverIp']];
}
}
Inside your anonymous function, "server" is still within the function scope. No need to pass it in as an argument.
The Quick Fix
// ...
div.slideUp('fast', function() { // `server` argument removed
// ...
});
The Explanation
There is no need to pass server to the function. The anonymous function "closes" over the server variable.
This is merely a function declaration:
function (server) {...}
You aren't passing anything to the function yet, as it isn't being invoked yet! The (server) bit
in a function declaration simply lets you name the arguments to your function. Only when you invoke the function can you pass arguments:
var name = "Jill";
var showName = function (name) {
alert(name);
};
showName("Jack"); // alert box shows "Jack"
showName(); // alert box shows "undefined"
So, when you declare that the name of the first argument to your anonymous function is server, there is a name conflict which prevents the original from being accessible; the server in your anonymous function is whatever slideUp passes as the first argument, which, according to the documentation, is nothing, so server is now undefined.
If this is confusing (and I suspect it is), I would suggest reading about javascript closures. Here's a good place to get started.
Fun fact: you can actually access arguments, in order, without having any explicit names, by using Javascript's built in arguments array object inside a function:
var sum = function () {
var i, total = 0;
for(i = 0; i < arguments.length; ++i) {
total = total + arguments[i];
}
return total ;
};
alert(sum(1,2,3)); // Displays "6"
alert(sum(1,2,3,4)); // Displays "10"
alert(sum(1,0,2,3)); // Displays "6"
alert(sum()); // Displays "0"
I have a banner rotator and I wanted to use objects instead of functions so I could make the code more efficient. Anyway I can't seem to get setInterval to work. I think it's got something to do with the object reference. Can anybody explain this? Here's what I've got so far:
window.addEvent('domready', function() {
function set_banner(divid, array)
{
var banner = $(divid);
banner.set('html', '<img src="" alt=""/>');
var banner_link = $(divid).getElement('a');
var banner_image = $(divid).getElement('img');
var delay = 0;
for (var keys in banner1array) {
var callback = (function(key) { return function() {
banner.setStyle('opacity', 0);
var object = array[key];
for (var property in object) {
if (property == 'href') {
var href = object[property];
}
if (property == 'src') {
var src = object[property];
}
}
if (!banner.getStyle('opacity')) {
banner.set('tween', {duration:1000});
banner_link.setProperty('href', href);
banner_image.setProperty('src', src);
banner.tween('opacity', 1);
}
}; })(keys);
setTimeout(callback, delay);
delay += 21000;
}
}
var banner1 = set_banner('banner1', banner1array);
setInterval(function() {set_banner('banner1', banner1array);}, 84000);
var banner2 = set_banner('banner2', banner2array);
setInterval(function() {set_banner('banner2', banner2array);}, 84000);
});
A couple of simple mistake:
var banner1 = new set_banner('banner1');
^ ---------- creates a new object and uses set_banner as the constructor
your code already gets called here
and you get a new object back, which in this case has NO use
....
setInterval(banner1(), 42000);
^----------------- The parenthesis EXECUTE the function
the RETURN VALUE is then passed to setInterval
BUT... banner1() is NOT a function, so this fails
What you want to do in case that you want to call set_banner after 42 seconds AND pass a parameter is to use an anonymous function which then calls set_banner.
setInterval(function() { // pass an anonymous function, this gets executed after 42 seconds...
set_banner('banner1'); // ...and then calls set_banner from within itself
}, 42000);
Something else to consider: http://zetafleet.com/blog/why-i-consider-setinterval-harmful.
(tl:dr Instead of setInterval, use setTimeout.) While I'm not sure that his arguments apply here, it seems like a good thing to get in the habit of avoiding.
function defer_banner(div, bannerArray, delay) {
setTimeout(function() {
setBanner(div, bannerArray);
defer_banner(div, bannerArray, delay);
}, delay);
});