I have a block of script that will produce a set of DOM. It has a loop that contains an asynchronous function (function A) and then function B after it. Function B's effect will be shown if the asynchronous function (A) is completely executed. I tried to use setTimeout function, but it doesn't work well, some of object outside setTimeout could not be accessed inside it.
Here's the script:
var parents = data.hasil.parent.split(">>");
var jmlparent = parents.length;
for (var i = jmlparent-2; i >= 1; i--){
add_parent(); // it's the asynchronous function (function A)
setTimeout(function (){
$("#parent_"+(jmlparent-i)).val(parents[(i-1)]); //(function B)
//returns error: parents is not defined
},200);
//i think 200ms is enough for browser to complete the execution of function A
}
Does anyone have the solution?
Function A should return a promise which when resolved you would call function B. You do not know how long function will take to resolve, therefore setTimeout() is not a good approach.
You also do need a closure:
var parents = data.hasil.parent.split(">>");
var jmlparent = parents.length;
for (var i = jmlparent-2; i >= 1; i--){
(function( i ) {
add_parent().done(function() {
$("#parent_"+(jmlparent-i)).val(parents[(i-1)]);
});
})( i );
}
I did not want to discuss the use of setTimeout but just improve you code. Try this :
...
for (var i = jmlparent-2; i >= 1; i--){
add_parent();
setTimeout(function (a){
$("#parent_"+(jmlparent-a)).val(parents[(a-1)]); //(function B)
},200, i); // we pass here the i var to setTiemout
}
About add_parent() why you call it inside the loop ? may be before the loop once is better ?! I dont know ...
Related
for university I have to create a javascript slideshow.
I use the function setTimeout( "other function", timeout).
My problem is that using this function recursive ( slideshow() ) works but when I try use it in a for loop ( altSlideshow() ) nothing happens.
// this one works
function slideShow() {
nextImg();
setTimeout(slideShow, timeOut); //Angabe Timeout in Millisekunden
}
// this one doesn't work
function altSlideshow(){
for(var x = 0; x <= 4; x++){
setTimeout(nextImg(), timeOut);
}
}
Thanks in advance!
Two problems here :
you pass nextImg() instead of the function nextImg
you schedule all the timeouts for the same instant (the delay you pass is computed from the time of execution of the loop)
Change
setTimeout(nextImg(), timeOut);
to
setTimeout(nextImg, timeOut*(x+1));
I think you want to pass a reference to the function, rather than using the returned value:
function altSlideshow(){
for(var x = 0; x <= 4; x++){
setTimeout(nextImg, timeOut);
}
}
Also, as dystroy mentions, you should make use of x to generate a unique timeout for each iteration.
function animateGraph() {
var graph;
for(i=0; i<10; i++)
{
var start = new Date();
while((new Date()) - start <= 500) {/*wait*/}
document.getElementById("timeMark").innerHTML = phoneX[i].epoch;
}
}
The loop works. The wait works. But the document.getElement is not showing up until the last item in the array...why?
Using setTimeout will allow the code to run and not lock up the page. This will allow it to run the code and will not effect other elements on the page.
var cnt = 0;
(function animateGraph() {
document.getElementById("timeMark").innerHTML = phoneX[cnt].epoch;
cnt++;
if (cnt<10){
window.setTimeout(animateGraph,500);
}
})();
The while loop, waiting for a datetime, is not a good way to wait - it just blocks execution. It keeps the browser (including UI, and its updating) frozen until the script finishes. After that, the window is repainted according to the DOM.
Use window.setTimeout() instead:
function animateGraph(phoneX) {
var el = document.getElementById("timeMark")
var i = 0;
(function nextStep() {
if (i < phoneX.length )
el.innerHTML = phoneX[i].epoch;
i++;
if (i < phoneX.length )
window.setTimeout(nextStep, 500);
})();
}
Please note that this runs asynchronous, i.e. the function animateGraph will return before all phoneXes are shown.
Use setTimeout instead of a while loop.
https://developer.mozilla.org/en/DOM/window.setTimeout
Also try something like this.
Javascript setTimeout function
The following snippet uses a helper function to create the timers. This helper function accepts a loop counter argument i and calls itself at the end of the timer handler for the next iteration.
function animateGraph() {
var graph;
setTimeMarkDelayed(0);
function setTimeMarkDelayed(i) {
setTimeout(function() {
document.getElementById("timeMark").innerHTML = phoneX[i].epoch;
if (i < 10) {
setTimeMarkDelayed(++i);
}
}, 3000);
}
}
You actually need some sort of helper function, otherwise you'll end up overwriting the value of i in your for loop in every iteration and by the time your timers run out, i will already be 9 and all handlers will act on the last element in phoneX. By passing i as an argument to the helper function, the value is stored in the local scope of that function and won't get overwritten.
Or you could use setInterval like Radu suggested, both approaches will work.
I'm trying to figure out how to generate functions inside for loop.
I have:
for (var i = fir_0_f.length - 1; i >= 0; i--){
var next = i+1;
var N = i;
// Attemps
//goal0_[i](next,N);
//eval('goal0_'+i+'('+next+', '+N+')');
};
Have done also some searching. [] expects a string, eval() is a B.A.D practice. Is there any other way?
How to set the timeout for each function later? So they would run sequentally?
Thanks a lot!
In JavaScript you could use function expressions to build an array of functions:
var goals = [];
goals.push((function (param1, param2) {
// your code for the first function
}));
goals.push((function (param1, param2) {
// your code for the second function
}));
// ... etc
Then in your for loop, you can simply reference your functions as elements of an array:
goals[i](next, N);
UPDATE:
To call your functions with a delay between each other, you'd have to change your loop logic. Instead of using a for loop, call the first function immediately, and then after it runs, make it call the second one using a setTimeout().
for (var i = fir_0_f.length - 1; i >= 0; i--){
var next = i+1;
var N = i;
setTimeout('goal0_'+i+'('+next+','+N+')', 0);
}
Note: errors thrown by goal0_i won't be caught by the loop.
I've noticed this behavior in Firefox.
That means that the following won't work as you expected:
try{
setTimeout(function_throwing_error, 0);
}
catch(e){
alert("I kill you!");
}
For global functions, you can just do:
window['goal0_'+i](next, N);
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.
this has interested me purely as research and personal development. i have a namespaced set of functions / variables.
within 1 function I need to call another through setTimeout but keeping the scope to 'this'. i am struggling with this a little, can't seem to bind it for when the setTimeout runs.
var foo = {
ads: ["foo","bar"],
timeDelay: 3,
loadAds: function() {
var al = this.ads.length;
if (!al)
return; // no ads
for(var i = 0; i < al; i++) {
setTimeout(function() {
this.scrollAd(this.ads[i]);
}.apply(this), this.timeDelay * 1000);
}
},
scrollAd: function(adBlock) {
console.log(adBlock);
}
};
};
the .apply(this) DOES change the scope as the console.log outputs the right object back, but it runs the function immediately and then the exception/warning comes up as the callback remains empty:
useless setTimeout call (missing quotes around argument?)
is there an elegant way of doing this at all? i know i could do
var _this = this;
and reference _this in the anon callback. for example, in mootools i'd use .bind(this) instead...
and no, as this involves animating, i don't want to use " " around the string as it will need to be eval'd and would impact performance...
for(var i = 0; i < al; i++) {
setTimeout(function() {
this.scrollAd(this.ads[i]);
}.apply(this), this.timeDelay * 1000);
}
apply doesn't bind a function, it calls it. So you execute the scroll straight away and then pass its return value (undefined) to setTimeout, which is ineffective.
You probably meant to use a closure like this over this and the loop variable (which must be closed or it will be the same, post-loop value for every timeout):
for(var i = 0; i < al; i++) {
setTimeout(function(that, j) {
return function() {
that.scrollAd(that.ads[j]);
};
}(this, i), this.timeDelay * 1000);
}
However you may prefer to use the new ECMAScript Fifth Edition function binding feature, which has a much more compact syntax:
for (var i= 0; i<al; i++)
setTimeout(this.scrollAd.bind(this, this.ads[i]), this.timeDelay*1000);
(There's an implementation of function.bind for browsers that don't have have it natively at the bottom of this answer.)
From what I know you should indeed use something like this:
var self = this;
setTimeout(function(){self.scrollAd(ad);}, this.timeDelay * 1000);
But if you badly want to use .apply(), then do it like this:
var self = this;
setTimeout(function(){
function(){
}.apply(self);
}, this.timeDelay * 1000);
Also note that if you run this inside a for loop and use i's value inside a function that is run in timer, then your function will always run with the last value of i (i.e. i == al). In order to fix that, you'll need to make a closure with each value of i separately.
So taking your code and making it work it should look like this:
var foo = {
ads: ["foo","bar"],
timeDelay: 3,
loadAds: function() {
function runTimed(o, fn, args, time)
{
setTimeout(function(){ fn.apply(o, args); }, time);
}
var al = this.ads.length;
if (!al)
return; // no ads
for(var i = 0; i < al; i++) {
runTimed(this, this.scrollAd, this.ads[i], this.timeDelay*1000);
}
},
scrollAd: function(adBlock) {
console.log(adBlock);
}
};
};
Note: I haven't run this code so it may contain some mistakes.
Also if I were you, I'd use the data from object and don't pass it to the scrollAd (i is enough).