setTimeout and anonymous function problem - javascript

This is my code, SetOpacity get invoked with wrong values, why?
function SetOpacity(eID, opacity){
eID.style.opacity = opacity / 100;
eID.style.filter = 'alpha(opacity=' + opacity + ')';
}
function fade(eID, startOpacity, endOpacity){
var timer = 0;
if (startOpacity < endOpacity) {
for (var i = startOpacity; i <= endOpacity; i++) {
setTimeout(function() {SetOpacity(eID, i);}, timer * 30);
timer++;
}
}
}

This should work:
for (var i = startOpacity; i <= endOpacity; i++) {
(function(opacity) {
setTimeout(function() {SetOpacity(eID, opacity);}, timer * 30);
})(i);
timer++;
}
This works as follows:
inside the loop you create an anonymous function (function(...){...}) and immediately call it with a parameter (that's why there are parentheses around function(){}, so you can call it by adding () at the end and passing parameters)
parameters passed to this anonymous function (in this case i, which is opacity inside the function) are local to this anonymous function, so they don't change during the next iterations of the loop, and you can safely pass them to another anonymous function (the first parameter in setTimeout)
Your original version didn't work because:
your function that is passed to setTimeout holds a reference to the variable i (not the value of it), and it resolves its value only when this function is called, which is not at the time of adding it to setTimeout
the value of this variable gets changed in the loop, and before even the first setTimeout executes, i will have reached endOpacity (the last value from the for loop)
Unfortunately JavaScript only has function scope, so it won't work if you create the variable inside the loop and assign a new actual value, because whenever there is some var inside a function, those variables are created at the time of function execution (and are undefined by default). The only (easy) way to create new scope is to create a function (which may be anonymous) and create new variables inside it (parameters are variables too).

This is a closure issue. By the time you run the function, i is already at endOpacity. This will work, by creating another closure:
function SetOpacityTimeout(eID, opacity, timer){
setTimeout(function() {SetOpacity(eID, opacity);}, timer * 30);
}
function fade(eID, startOpacity, endOpacity){
var timer = 0;
if (startOpacity < endOpacity) {
for (var i = startOpacity; i <= endOpacity; i++) {
SetOpacityTimeout(eID,i,timer);
timer++;
}
}
}

Kobi has the right idea on the problem. I suggest you use an interval instead, though.
Here's an example: (Your SetOpacity function remains the same, I left it out here.)
function fade(eID, startOpacity, endOpacity){
var opacity = startOpacity;
SetOpacity(eID, opacity);
var interval = window.setInterval(function(){
opacity++;
SetOpacity(eID, opacity);
// Stop the interval when done
if (opacity === endOpacity)
window.clearInterval(interval);
}, 30);
}

This is am example I used with jquery. "menuitem" is the itemclass and jquery checks the "recentlyOut" class to see if it needs to slide back up.
The code speaks for itself.
$(".menuitem").mouseenter(
function(){
$(this).addClass("over").removeClass("out").removeClass("recentlyOut");
$(this).children(".sub").slideDown();
});
$(".menuitem").mouseleave(
function(){
$(this).addClass("out").addClass("recentlyOut").removeClass("over");
setTimeout(function()
{
var bool = $(".recentlyOut").hasClass("over");
if (!bool)
{
$(".recentlyOut").removeClass("recentlyOut").children(".sub").slideUp();
}
}
, 400);
}
);

Related

Store setTimeout and call it later

Look at the following code:
var timer=setTimeout(function(){increase();}, 8);
This setTimeout function will be executed immediately, but I want
it to execute later. Why?
Example:
function increase(i)
{
i++; if(i==100) return;
var timer=setTimeout(function(){increase(i);}, 8);
}
Now, I need to stop and exit this function within another function when certain thing happen:
if (a==b) clearTimeout(timer);
The thing that bothers me is that variable timer is getting assigned, whenever
function increase runs, but it does not need to, and I believe it is bad practice. That is why I need to assign to it only once, before function run and execute it later when need arrives.
I hope you understood, and btw, those are just examples, not my code.
You can declare a variable outside of function the calls setTimeout, define the variable as setTimeout when the function is called; call clearTimeout() from another function with variable referencing setTimeout as parameter.
var timer = null, // declare `timer` variable
n = 0, // reference for `i` inside of `increase`
i = 0,
a = 50,
b = 50,
// pass `increase` to `t`, call `increase` at `setTimeout`
t = function(fn, i) {
// define timer
timer = setTimeout(function() {
fn(i)
}, 8)
};
function increase(i) {
console.log(i);
// set `n` to current value of `i` to access `i`:`n`
// to access `i` value outside of `t`, `increase` functions
n = i++;
if (i == 100) return;
t(increase, i); // call `t`
}
increase(i);
// do stuff outside of `t`, `increase`
setTimeout(function() {
// clear `timer` after `200ms` if `a == b`
if (a == b) {clearTimeout(timer)};
alert(n)
}, 200)
If you want the operation of one function to change the conditions of another, just declare a boolean variable within the scope of both functions and change it's value depending on a terminator function.
For example, take a look at this code:
var exit = false;
function increase(i) {
if(i==100 || exit) return;
setTimeout(function(){ increase(++i) }, 1000);
}
function terminator(a, b){
exit = (a==b);
}
increase(0);
Here, if terminator is ever called with a pair of equal arguments like:
setTimeout(function(){ terminator(true, 1) }, 5000) // timeout of 5 seconds means increase will run 5 times
the recursive call of setTimeout within the increase function will not be reached (after 5 seconds), as the function will return before reaching that line of code.
If terminator is never called, or called with unequal arguments like:
setTimeout(function(){ terminator(true, false) }, 5000) // using setTimeout here is just arbitrary, for consistency's sake
increase will only time out once it's completed 100 recursions (in other words, after 100 seconds have elapsed)
Hope this helps!
Because the delay in setTimeout takes millisecond as time unit, so in your code, you set your function to be executed after 8ms, which feels like immediately.
function increase(i, boolean) {
i++;
if (i == 100) return;
if (boolean) {
var timer = setTimeout(function() {
increase(i, true);
console.log(i);
}, 8);
}
}
increase(1,true);
What about you put in some extra argument
to the function?

Placing timer ID into its function

I would like to put timer ID, returned by setInterval(), into its function:
delay_timeout = setInterval(function () {
test_delay(data['time'], delay_timeout);
}, 1000);
Is it possible? To my mind delay_timeout doesn't have a value at this point...
I don't want to save delay_timeout globally for using later in timer's function to stop it. Several timers may work at the same time.
UPDATE:
Code is not global, it is located here:
socket.on('test_delay', function (data) {
...
});
The point is not to make delay_timeout global and be able to kill timer by some condition within its callback function.
your code works fine if you put the setTimeout call in it's own function like this:
function setTimer(){
var timeId = setTimeout(function(){ console.log(timeId); }, 1);
}
for(var i = 0; i < 10; i ++){
setTimer();
}
here's a fiddle
The delay_timeout variable is available to your callback as it is in the same enclosure.
So long as you are not in the same context and rerun your setTimeout before it has triggered the callback you will be fine. so this won't work:
var timeId;
for(var i = 0; i < 10; i ++){
timeId = setTimeout(function(){ console.log(timeId); }, 1);
}
(see the second half of the fiddle...)

setInterval exits after first iteration. Please help me rectify this snippet?

Can someone help me rectify the issue related to the setInterval? I'm fairly new to JavaScript, I'm not sure what's wrong here. I have this block in my page:
GlobalTicker.prototype.TickElements = function () {
this.timer = setInterval(this.initializeElement.apply(this) , 1000);
};
GlobalTicker.prototype.initializeElement = function () {
for (var i = 0; i < this.tickerElements.length; i++) {
var existingRun = this.tickerElements[i].secs;
var elementId = $('#' + this.tickerElements[i].id + ' .comment-editor').find('.ticker');
existingRun -= 1;
$(elementId).text(existingRun);
if (existingRun === 0) {
$(elementId).remove();
this.tickerElements.splice(i, 1);
if (this.tickerElements.length == 0) clearInterval(this.tickerElements.timer);
}
}
};
Then somewhere in the code, I have this call in a function
var objTicker = new GlobalTicker();
CommentManagement.prototype.createComment = function (domObject) {
objTicker.TickElements();
};
This function call actually invokes the setInterval function and runs the first iteration and jumps to the initialiseComment(); but once this block is executed, on the next interval, instead of executing the initialiseComment(); again, it jumps back to my function call CreateComment();. What am I doing wrong here?
setInterval() requires a function reference. You were calling the function itself and passing the return result from executing the function (which was undefined) to setInterval(). Since that return value is not a function, there was no callback function for setInterval() to call. Thus, your method was executed once when you first called it, but couldn't be called by the interval.
To fix it, you should change from this:
this.timer = setInterval(this.initializeElement.apply(this) , 1000);
to this:
var self = this;
this.timer = setInterval(function() {self.initializeElement()}, 1000);
Note, the value of this will also be different in the setInterval() callback than the value you want so the one you want is saved here in self so it can be referenced from that. There's also no need to use .apply() in this case because calling a method on an object will automatically set the this pointer as needed.

Return a function from the anonymous wrapper?

I am trying to undrstand the code
for(var i = 0; i < 10; i++) {
setTimeout((function(e) {
return function() {
console.log(e);
}
})(i), 1000)
}
from here http://bonsaiden.github.com/JavaScript-Garden/#function.closures
I understood this method :
for(var i = 0; i < 10; i++) {
(function(e) {
setTimeout(function() {
console.log(e);
}, 1000);
})(i);
}
Can anyone please help me by explaining the first one?
I will try to explain how I understands the first one,
first i is 0,
setTimeout is called,
self calling function "function(e)" is called with i=0,
Im stuck!! what happens when this function returns a function?
All the first one does is return a function that will be called after the timeout happens.
The purpose of it is to create a sub-scope for each iteration of the for loop so that the incrementing i isn't overridden with each iteration.
More explanation:
Lets take this apart into two different pieces:
for(var i = 0; i < 10; i++) {
setTimeout((function(e) {
return function() {
console.log(e);
}
})(i), 1000)
}
This is the first piece:
for(var i = 0; i < 10; i++) {
setTimeout(function(){
console.log(i); //9-9
},1000);
}
Now, when you run this loop, you will always get console.log()'s that contain 9 instead of 0 to 9. This is because each setTimeout is using the same reference to i.
If you wrap the setTimeout part of that in an anonymous function, it creates a scope for each iteration allowing each setTimeout to have it's own i value.
for(var i = 0; i < 10; i++) {
setTimeout((function(i) {
return function() {
console.log(i); // 0-9
}
})(i), 1000)
}
The outer function inside the setTimeout gets executed immediately with an i of 0 for first iteration, 1 for second, etc. That function then in turn returns a function which is the function that setTimeout uses. A function is being generated and returned for each iteration of the loop using a different value for i.
Both end up with the same result: a setTimeout is called with a function to invoke, which writes a number from 0 to 9 on the console. Both use nested functions to get the current value of i into a closure so you don't end up logging 10 9's.
The first code chooses to have a function returning the function that setTimeout will call. The second changes the nesting order so that the closed-over function invokes setTimeout itself. The net effect is the same.
Other than stylistic reasons and personal choice, I don't see a reason to choose one over the other.
"Can you please check the updated question specifying where im getting confused"
OK, here's the long explanation. Remember that the first parameter to setTimeout() needs to be a reference to the function that you want executed after the specified delay. The simplest case is to just name a function defined elsewhere:
function someFunc() {
console.log("In someFunc");
}
setTimeout(someFunc, 100);
Note there are no parentheses on someFunc when passing it as a parameter to setTimeout because a reference to the function itself is required. Contrast with:
setTimeout(someFunc(), 100); // won't work for someFunc() as defined above
With parenthese it calls someFunc() and passes its return value to setTimeout. But my definition of someFunc() above doesn't explictly return a value, so it implicitly returns undefined - which is like saying setTimeout(undefined, 100).
But it would work if changed someFunc() to return a function instead of returning undefined:
function someFunc() {
return function() {
console.log("In the function returned from someFunc");
};
}
So now (at last) we come to the code from your question:
setTimeout((function(e) {
return function() {
console.log(e);
}
})(i), 1000)
Instead of referencing a function by name and calling it as someFunc(i) it defines an anonymous function and calls it immediately as (function(e) {})(i). That anonymous function returns another function and it is that returned function that becomes the actual parameter to setTimeout(). When the time is up it is that returned function that will be executed. Because the (inner) function being returned is defined in the scope of the (outer) anonymous function it has access to the e parameter.

Javascript: how to pass different object to setTimeout handlers created in a loop?

I'm trying to write some JS replicating jQuery's fadeIn and fadeOut functions. Here's the code I have so far:
function fadeIn(elem, d, callback)
{
var duration = d || 1000;
var steps = Math.floor(duration / 50);
setOpacity(elem,0);
elem.style.display = '';
for (var i = 1; i <= steps; i++)
{
console.log(i/steps + ', ' + (i/steps) * duration);
setTimeout('setOpacity("elem", '+(i / steps)+' )', (i/steps) * duration);
}
if (callback)
setTimeout(callback,d);
}
function setOpacity(elem, level)
{
console.log(elem);
return;
elem.style.opacity = level;
elem.style.MozOpacity = level;
elem.style.KhtmlOpacity = level;
elem.style.filter = "alpha(opacity=" + (level * 100) + ");";
}
I'm having troubles with the first setTimeout call - I need to pass the object 'elem' (which is a DOM element) to the function setOpacity. Passing the 'level' variable works just fine... however, I'm getting "elem is not defined" errors. I think that's because by the time any of the setOpacity calls actually run, the initial fadeIn function has finished and so the variable elem no longer exists.
To mitigate this, I tried another approach:
setTimeout(function() { setOpacity(elem, (i / steps));}, (i/steps) * duration);
The trouble now is that when the function is called, (i/steps) is now always 1.05 instead of incrementing from 0 to 1.
How can I pass the object in question to setOpacity while properly stepping up the opacity level?
Your "another approach" is correct, this is how it's usually done.
And as for the problem of i always being a constant, that's how closures work!
You see, when you create this function that does something with i (like function() { alert(i); }), that function, as they say, 'captures', or 'binds' the variable i, so that variable i does not die after the loop is finished, but continues to live on and is still referenced from that function.
To demonstrate this concept, consider the following code:
var i = 5;
var fn = function() { alert(i); };
fn(); // displays "5"
i = 6;
fn(); // displays "6"
When it is written in this way, the concept becomes a bit more evident, doesn't it? Since you're changing the variable in the loop, after the loop is finished the variable retains it's last value of (1+steps) - and that's exactly what your function sees when it starts executing.
To work around this, you have to create another function that will return a function. Yes, I know, kind of mind-blowing, but bear with me. Consider the revised version of my example:
function createFn( theArgument )
{
return function() { alert( theArgument ); };
}
var i = 5;
var fn = createFn( i );
fn(); // displays "5"
i = 6;
fn(); // still displays "5". Voila!
This works, because the fn function no longer binds the variable i. Instead, now it binds another variable - theArgument, which has nothing to do with i, other than they have the same value at the moment of calling createFn. Now you can change your i all you want - theArgument will be invincible.
Applying this to your code, here's how you should modify it:
function createTimeoutHandler( elemArg, iDivStepsArg )
{
return function() { setOpacity( elemArg, iDivStepsArg ); };
}
for (var i = 1; i <= steps; i++)
{
console.log(i/steps + ', ' + (i/steps) * duration);
setTimeout( createTimeoutHandler( elem, i/steps ), (i/steps) * duration);
}
Your first approach is evaluating code at runtime. You are most likely right about why it's failing (elem is not in the scope in which the code is eval'd). Using any form of eval() (and setTimeout(string, ...) is a form of eval()) is a general bad idea in Javascript, it's much better to create a function as in your second approach.
To understand why your second approach is failing you need to understand scopes and specifically closures. When you create that function, it grabs a reference to the i variable from the fadeIn function's scope.
When you later run the function, it uses that reference to refer back to the i from fadeIn's scope. By the time this happens however, the loop is over so you'll forever just get i being whatever it was when that loop ended.
What you should do is re-engineer it so that instead of creating many setTimeouts at once (which is inefficient) you instead tell your setTimeout callback function to set the next Timeout (or you could use setInterval) and do the incrementing if your values inside that callback function.

Categories