SitePoint Sponsor

User Tag List

Results 1 to 9 of 9
  1. #1
    Guru in training bronze trophy SoulScratch's Avatar
    Join Date
    Apr 2006
    Location
    Maryland
    Posts
    1,838
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    setTimeout, loop, closures?

    onload = function() {
    var items = ['earth', 'jupiter', 'pluto', 'mars', 'neptune', 'uranus', 'etc'];
    var time = 500;
    for (var i=0, l=items.length; i<l; ++i) {
    time+=500;
    var foo = document.createElement('p');
    foo.appendChild(document.createTextNode(items[i]));
    function callLater() {
    return (function() {
    document.body.appendChild(foo);
    });
    }
    var functRef = callLater();
    unique = window.setTimeout(functRef,time);
    }
    };
    So I have a for loop here that iterates through an array and on each iteration i create a new element and append a text node, and i'm trying to set a timer via setTimeout so there's some latency between the appending, however it's only appending the last item. I'm pretty sure this has to do with closures and I've tried all I could it seems.

    The end result I'd like, again, is for the paragraphs to be appended in order, so the paragraph with 'earth' would appear, then after some time 'jupiter', et cetera.

    Thanks in advance.

  2. #2
    Guru in training bronze trophy SoulScratch's Avatar
    Join Date
    Apr 2006
    Location
    Maryland
    Posts
    1,838
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Wow.

    Seems like it was just using the last value of i so I took the code that generated the text node and put it in a function that takes the i parameter after being invoked from setTimeout..

    Code JavaScript:
    	onload = function() {
    	    var items = ['earth', 'jupiter', 'pluto', 'mars', 'neptune', 'uranus', 'etc'];
    	    var time = 500;
    	    for (var i=0, l=items.length; i<l; ++i) {
    		time+=500;
    		function callLater(i) { 
    		    return (function() {
    		    var foo = document.createElement('p');
    		    foo.appendChild(document.createTextNode(items[i]));
    			console.log(foo.innerHTML);
    			document.body.appendChild(foo);
    		    });
    		}
    		var functRef = callLater(i);
    		unique = window.setTimeout(functRef,time);
    	    }
    	};

  3. #3
    Guru in training bronze trophy SoulScratch's Avatar
    Join Date
    Apr 2006
    Location
    Maryland
    Posts
    1,838
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Would anyone happen to know how I could change the syntax so instead of feeding a function reference to setTimeout as the first parameter, just feed a function literal instead?

  4. #4
    Unobtrusively zen silver trophybronze trophy
    paul_wilkins's Avatar
    Join Date
    Jan 2007
    Location
    Christchurch, New Zealand
    Posts
    14,678
    Mentioned
    99 Post(s)
    Tagged
    4 Thread(s)
    Wrap it in an anonymous function.

    Code javascript:
    unique = window.setTimeout(function () {
        callLater(i);
    },time);

    Edit: The i variable won't hold its value unless it's wrapped in another function that maintains the value, as shown below.
    Last edited by paul_wilkins; Jul 26, 2008 at 17:00.
    Programming Group Advisor
    Reference: JavaScript, Quirksmode Validate: HTML Validation, JSLint
    Car is to Carpet as Java is to JavaScript

  5. #5
    Guru in training bronze trophy SoulScratch's Avatar
    Join Date
    Apr 2006
    Location
    Maryland
    Posts
    1,838
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    That's what I tried at first, but it seems the reference does not hold up and needs to be there. Damn, closures are hard to grasp.

  6. #6
    SitePoint Zealot Amenthes's Avatar
    Join Date
    Oct 2006
    Location
    Bucharest, Romania
    Posts
    143
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    How about this?

    Code JavaScript:
    onload = function() {
        var items = ['earth', 'jupiter', 'pluto', 'mars', 'neptune', 'uranus', 'etc'];
        var time = 500;
     
        for (var i=0, l=items.length; i<l; ++i) {
            time+=500;
     
            unique = window.setTimeout((function(i) {
                return (function() {
                    var foo = document.createElement('p');
                    foo.appendChild(document.createTextNode(items[i]));
                    console.log(foo.innerHTML);
                    document.body.appendChild(foo);
                });
            })(i),time);
        }
    };
    I'm under construction | http://igstan.ro/

  7. #7
    Guru in training bronze trophy SoulScratch's Avatar
    Join Date
    Apr 2006
    Location
    Maryland
    Posts
    1,838
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    That works, awesome! Would you happen to know the reason why it does so?

  8. #8
    SitePoint Zealot Amenthes's Avatar
    Join Date
    Oct 2006
    Location
    Bucharest, Romania
    Posts
    143
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I basically did what you did, but with a lambda executed right after its definition, so basically I avoid creating a closure for the i variable. I pass the variable as a parameter so that the actual function that gets into setTimeout has an i with a real value, not an i with a promise to have a value at its containing function execution.

    So this line:

    foo.appendChild(document.createTextNode(items[i]));

    It's actually:

    foo.appendChild(document.createTextNode(items[0]));

    for the first iteration and so on.

    With a closure, like the one proposed by pmw57, you actually have a placeholder with a reference to i which at the moment of execution will look after the value of i, which in your case was the length of items.

    Now, assuming that between the 3rd and 4th iteration a timeout would trigger, the value that would be have been used was 2 (0, 1, 2). But that's not happening because a for loop is fast enough.

    Now, a shorter version using your own code, could have been:
    Code JavaScript:
    unique = window.setTimeout(callLater(i) ,time);
    and that may be better because avoids creating different execute-in-place lambdas when all there is different to them is the value of the parameter.
    I'm under construction | http://igstan.ro/

  9. #9
    SitePoint Evangelist
    Join Date
    Jul 2007
    Posts
    345
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Another option is to wrap the whole loop in a function, passing in i as a parameter. It has the same effect but simplifies the code a little.
    Code:
    onload = function() {
        var items = ['earth', 'jupiter', 'pluto', 'mars', 'neptune', 'uranus', 'etc'];
        var time = 500;
        
        for (var i=0, l=items.length; i<l; ++i) {
            (function(i){
               time+=500;
            
               unique = window.setTimeout(function() {
                    var foo = document.createElement('p');
                    foo.appendChild(document.createTextNode(items[i]));
                    console.log(foo.innerHTML);
                    document.body.appendChild(foo);
               }, time);
            })(i);
        }
    };
    For a discussion and more examples of this use of anonymous functions and closures see http://www.room51.co.uk/js

    Also, regarding timeouts triggering during loops, you'll find that because JavaScript running in a browser is single threaded, the timeout won't get a look in until the function finishes. Even if you set the timeout to 0 milliseconds it will still wait until the function is finished before firing.


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •