why does this setTimeout() call work in the console but not as a greasemonkey script?

When I use a setTimeout() in a for() loop in a greasemonkey script, it doesn't appear to work at all. However, the exact same code works fine if I run it in the Firebug console. Here's the code:

// ==UserScript==
// @name           setTimeout test
// @include        *
// @run-at         document-end
// ==/UserScript=


function test(delaytime) {
    alert("test called with "+delaytime);
}

function test2() {
  for( var i = 0; i < 100; i+= 10 ) {
    setTimeout('test('+i+');', i);
  }
}

setTimeout(test2,10);

If I replace the for() loop with explicit calls like the following, then it works fine.

setTimeout(function() { test( 0); },  0);
setTimeout(function() { test(10); }, 10);
setTimeout(function() { test(20); }, 20);
setTimeout(function() { test(30); }, 30);
setTimeout(function() { test(40); }, 40);
setTimeout(function() { test(50); }, 50);
setTimeout(function() { test(60); }, 60);
setTimeout(function() { test(70); }, 70);
setTimeout(function() { test(80); }, 80);
setTimeout(function() { test(90); }, 90);

What's the difference? Is there any way I can get the for loop generated setTimeouts to work in greasemonkey?

Answers:

Answer

Because when the string is evaled at the time the setTimeout fires in order to execute the function, the loop has run it's course and i is sitting at the last value of the loop.

To freeze the value of i for each call to setTimeout, you need to capture it in a function closure like this:

function test2() {
  for( var i = 0; i < 100; i+= 10 ) {
    setTimeout(function(val) {
        return(function() {test(val);});
    } (i), i);
  }
}

This also has the advantage of getting rid of the eval in the setTimeout parameter.

Answer

You have to duplicate the value of i into a locally scoped variable:

function test2() {
  for( var i = 0; i < 100; i+= 10 ) {
    (function(i){
        setTimeout('test('+i+');', i);
    })(i);
  }
}

On a side note: You should pass an anonymous function to setTimeout instead of passing it a string (which will be eval'ed). It's much faster this way:

function test2() {
    for( var i = 0; i < 100; i+= 10 ) {
        (function(i){
            setTimeout(function(){
                test(i);
            }, i);
        })(i);
    }
}

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.