2

I have an array of URLs, and an array of data that I'm pulling externally. The URLs and the data correspond one to one perfectly. I try to do this:

for (var i = 0; i < children.length; i++) {
    urls.push(children[i].data.url); 
}

for (var i = 0; i < children.length; i++) { 
    $.ajax({
         url: DATA_SOURCE,
         dataType: "jsonp",
         success: function (data2) {                           
             console.log(urls[0]);
             console.log(urls[i]);
         }
    });                        
}

The first log always prints the first element of the urls array. I can change the number to any other valid index and it works. The second log however always prints undefined. I initially grabbed urls from within the second loop, but I took it out just in case it turned out to be an asynchronous error or something. urls is initialized to be an Array, not an Object. Taking the second log out of the success function (and into the main scope of the for loop) makes it perform as expected. I'd be happy to chalk it up to the ajax request screwing it up somehow, but then why does the first log perform as expected?

Any ideas?

y2bd
  • 5,783
  • 1
  • 22
  • 25
  • instead of using urls as another array why can't you just put children[i].data.url directly into the ajax – fuzionpro Oct 18 '12 at 07:15

3 Answers3

2

$.ajax is asynchronous, this means that when the callbacks (success) run, the loop has already finished running and it is now set to children.length

Accessing urls[ i ], equals to urls[ children.length therefore is undefined.

An easy fix for this, would be to create scopes for each iteration

for (var i = 0; i < children.length; i++) { 
  (function( index ) {
    $.ajax({
       url: DATA_SOURCE,
       dataType: "jsonp",
       success: function (data2) {                           
         console.log(urls[ index ]);
        }
    }); 
  })( i );                       
}
Pantelis
  • 6,086
  • 1
  • 18
  • 21
1

+1 to Pantelis' answer, though just a slightly pedantic remark: there's no real need to wrap the entire ajax call in an IIFE, only the success callback needs to reference the "current" value of i, so this is will do just fine:

for (var i = 0; i < children.length; i++)
{
    $.ajax({
       url: DATA_SOURCE,
       dataType: "jsonp",
       success: ((function( index ) 
       {
           return function (data2)
           {
               console.log(urls[ index ]);
           };
       })(i)
    });
}
Elias Van Ootegem
  • 74,482
  • 9
  • 111
  • 149
  • How is this different from what i posted? – Asad Saeeduddin Oct 18 '12 at 07:30
  • @Asad: It isn't, but when I started to type this up, your answer wasn't updated yet. I just posted this to add to Pantelis' answer... The only difference is that you claim `i` would be undefined, while in fact, it isn't – Elias Van Ootegem Oct 18 '12 at 07:34
  • 1
    Cool, that's really nice! The only reason I choose to wrap the entire ajax call, is that the i might be needed in the rest of the callbacks (error, progress) too. But I like your solution more for this scenario. – Pantelis Oct 19 '12 at 22:18
0

This is because i is undefined when the success function is finally called. You need to pass the current value of i to the success function you are generating.

for (var i = 0; i < children.length; i++) { 
    $.ajax({
         url: DATA_SOURCE,
         dataType: "jsonp",
         success: (function (count) {
             return(function(data2){console.log(urls[0]); console.log(urls[count]);});        
         })(i)
    });                        
}

Here, the success function generated for each iteration is subtly different, in that it logs a different value.

Asad Saeeduddin
  • 46,193
  • 6
  • 90
  • 139
  • `i` isn't undefined, it'll be equal to `children.length` when the success function is finally called. Because the success callback references `i`, it won't go out of scope, but the value will be incremented time and time again – Elias Van Ootegem Oct 18 '12 at 07:21
  • the success function is called in the global context, no? and since he is using (correctly) `var i = 0`, i will not be accessible in the context of the success handler. Unless these loops themselves are running in the global context, in which case you're right. – Asad Saeeduddin Oct 18 '12 at 07:23
  • Nope, it's not a question of the context in which the success function is called: it's declared a member of an object literal (`$.ajax({object:"literal"});`), so whatever variable is referenced by that object's methods will not be GC'ed, regardless of the context in which the function object/method is called [Here's an answer to a question on closures and scopes](http://stackoverflow.com/questions/12675276/inheritance-with-knockout-js/12675582#12675582) that contain some examples of what I'm on about: calling an object in another context and closure references (the bit about `self`) – Elias Van Ootegem Oct 18 '12 at 07:29
  • @EliasVanOotegem It would appear I understand javascript scope less than I had assumed. I'm looking at some articles now, thanks for pointing that out. – Asad Saeeduddin Oct 18 '12 at 08:04