1

Here's a simplification of my JS program. mylist is an array of strings and MyAsync is a function that takes an object and arranges for the supplied callback to be called at a later time.

for (var myindex = 0; myindex < mylist.length; myindex += 1) {      
    MyAsync(mylist[myindex], function () { alert(myindex); });
}

When there's ten items in mylist, the result is two alert messages each showing "10". Okay, it's clearly using the value of myindex after the loop finishes. So I make a slight change...

for (var myindex = 0; myindex < mylist.length; myindex += 1) {
    var copyindex = myindex;
    MyAsync(mylist[myindex], function () { alert(copyindex); });
}

Now, each alert shows "9".

How do I please arrange for the callback function to know what myindex was at the time MyAsync was invoked?

Asynchronously, billpg.

billpg
  • 3,195
  • 3
  • 30
  • 57

2 Answers2

0

Yeah, as the comments and not making functions in a loop for the reason you are experiencing.

We can use a recursive function instead :

 var len = mylist.length;

 function asyncLoop(index) {
    MyAsync(mylist[index], function () { 
    if(index < len) {
        asyncLoop(++index);
     }
    });
  } 
  asyncLoop(0);

As comments , - firing them all off at the same time* ( *- quick as a loop can run ). and keeping track of the array count ...

 function asyncDo(index) {
    MyAsync(mylist[index], function () { 
      /* we have access to the array index here */
      console.log(index);
    });
  } 

  for ( var i=0, len=mylist.length; i<len; ++i) { asyncDo(i); }
Community
  • 1
  • 1
Rob Sedgwick
  • 5,216
  • 4
  • 20
  • 36
  • Won't that delay each subsequent MyAsync call until the prior one has completed? – billpg Apr 03 '14 at 18:03
  • yes, it will ( you don't want that ? ) I thought you did, as you wanted to track the index ? – Rob Sedgwick Apr 03 '14 at 18:04
  • I want all of the MyAsync calls to be fired off at the same time, but when each one finishes, the callback knows which one. – billpg Apr 03 '14 at 18:06
  • "knows which one", - as in the position in the array ? - will update – Rob Sedgwick Apr 03 '14 at 18:07
  • That's interesting, many thanks. How come your version works but mine using the copy of the index didn't? As far I can tell, the only difference is that you are using a named function rather than my anonymous function wrapping the alert call. – billpg Apr 03 '14 at 18:22
  • @billpg: You are not creating a new scope to capture the value of the loop variable. – Felix Kling Apr 03 '14 at 18:30
  • because the context/scope of `index` is lost ( in short, the `for` loop runs faster than the async call) - when the async returns, `index` has 'moved on'. Calling an outside function and passing the `index` as an argument, givesthe `index` variable it's own/new scope for the async to use. – Rob Sedgwick Apr 03 '14 at 18:31
0

You can use closures to do this: here is some code that I think demonstrates what you want, printing out 0 .. 9.

var funcs = [];

for(var i = 0; i < 10; i++){
    funcs.push( (function(j){
        return function(){
            console.log(j);
        };
    })(i));
}

funcs.forEach(function(f){
    f();
});

The idea is that in returning a function to be added to the list, the value of i is captured when that function was constructed from the parameter to the outer function.

Here's an SO post that helped me with this: JavaScript closures vs. anonymous functions

Community
  • 1
  • 1
anrosent
  • 11
  • 2
  • 2
    *"You can use closures to do this"* No you can't. Closures are the *reason* for this problem. The function the OP creates in the loop are already closures. The solution is to *create a new scope* by executing a function. Whether that function is a closure or not is irrelevant. Terminology matters ;) – Felix Kling Apr 03 '14 at 18:29