1

Here what I'm trying to do. I'm having an array like the following

var my_array = ['1', '2', '3' ... ,'1000000000000000'];

What I want to do is create a bunch of HTML elements for every element of that array, and since the array can contain a huge number of elements I attempted to do the following so the browser won't freeze.

for(var i in my_array)
{
    if(my_array.hasOwnProperty(i))
    {
       setTimeout(function(){
             do_something_with_data(my_array[i]);
       });
    }
 }

What happens though is that the my_array[i] within the setTimeout doesn't have the value it should.

To be more accurate, when I try to console.log(my_array[i]) what I get is something like this:

"getUnique" function (){
   var u = {}, a = [];
   for(var i = 0, l = this.length; i < l; ++i){
      if(u.hasOwnProperty(this[i])) {
         continue;
      }
      a.push(this[i]);
      u[this[i]] = 1;
   }
   return a;
}

getUnique is a function I've added to the Array prototype just like this:

Array.prototype.getUnique = function(){
   var u = {}, a = [];
   for(var i = 0, l = this.length; i < l; ++i){
      if(u.hasOwnProperty(this[i])) {
         continue;
      }
      a.push(this[i]);
      u[this[i]] = 1;
   }
   return a;
};

Can please somebody help me with this issue?

3 Answers 3

3

the setTimeout is executed after the loop is done, and i is the last key or some garbage value at that point. You can capture the i like so:

for (var i in my_array) {
    if (my_array.hasOwnProperty(i)) {
        (function(capturedI) {
            setTimeout(function() {
                do_something_with_data(my_array[capturedI]);
            });
        })(i);
    }
}

You should also not use for..in loops for arrays because it's an order of magnitude slower (especially so with the .hasOwnProperty check) than a for loop and the iteration order is not defined

If you have jQuery or willing to add some extra code for older browsers, you can do:

my_array.forEach( function( item ) {
     setTimeout( function() {
         do_something_with_data( item );
     }, 1000);
});

With jQuery:

$.each( my_array, function( index, item ) {
     setTimeout( function() {
         do_something_with_data( item );
     }, 1000);
});

See docs for [].forEach

Sign up to request clarification or add additional context in comments.

7 Comments

I would strongly recommend breaking out the factory function and using a different name for the i argument. This is very confusing to read, to debug, indeed to understand for people less versed in JS than I know you are. :-)
@ Esailija: It does the job! :-) (And looks like just a minor change to their code, if you don't really know what's going on...)
@T.J.Crowder Although I think [].forEach and $.each are better for this as they just work.
@ Esailija: Agreed, I'm a huge fan of forEach ever since I learned to stop worrying about the overhead. :-)
@Loupax: Just to be clear: You're creating a function either way. Doing it explicitly is clearer, easier to maintain, and if all of this is wrapped up in a function (as I assume it is), doesn't have any more impact than the constant re-evaluation of an anonymous function expression. (In fact, arguably less.) (Also to be clear: Please don't swap to accepting my answer, I'm just pointing out that this is not simpler code.)
|
2

The problem is that the functions you're creating have a reference to the i variable, not a copy of its value, and so when they run they see i as it is at that point in time (past the end of the array, presumably). (More: Closures are not complicated)

I'd recommend a completely different approach (below), but first, let's look at how to make your existing approach work.

To do what you were trying to do, with the for loop, you have to have the functions close over something that won't change. The usual way to do that is to use a factory function that creates the timeout functions such that they close over the argument to the factory. Or actually, you can pass in the array element's value rather than the index variable.

for(var i in my_array)
{
    if(my_array.hasOwnProperty(i))
    {
       setTimeout(makeFunction(my_array[i]));
    }
 }

function makeFunction(entry) {
    return function(){
        do_something_with_data(entry);
    };
}

But, I would probably restructure the code so you're not creating masses and masses of function objects unnecessarily. Instead, use one function, and have it close over an index that it increments:

// Assumes `my_array` exists at this point, and that it
// has at least one entry
var i = 0;
setTimeout(tick, 0);
function tick() {
    // Process this entry
    if (my_array.hasOwnProperty(i)) {
        do_something_with_data(my_array[i]);
    }

    // Move to next
    ++i;

    // If there are any left, schedule the next tick
    if (i < my_array.length) {
        setTimeout(tick, 0);
    }
}

Comments

0

Its just a guess. Try it like:

for(var i in my_array)
{
    if(my_array.hasOwnProperty(i))
       setTimeout("do_something_with_data('"+my_array[i]+"')", 500);
}

2 Comments

Don't use strings as the first parameter of setTimeout/setInterval
As a clarification, using a string as the first param actually relies on eval which is a Bad Thing.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.