22

I expected the code below to alert "0" and "1", but it alert "2" twice. I don't understand the reason. Don't know if it is a problem of jQuery. Also, please help me to edit title and tags of this post if they are inaccurate.

<html>
    <head>
        <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
        <script type="text/javascript">
            $(function() {
                for (var i=0; i<2; i++) {
                    $.get('http://www.google.com/', function() {
                        alert(i);
                    });
                }
            });
        </script>
    </head>
    <body>
    </body>
</html>
gylaz
  • 13,221
  • 8
  • 52
  • 58
Ethan
  • 18,584
  • 15
  • 51
  • 72
  • 2
    @chaos: Right beneath "Don't do HTML with regex", I guess. ;) – Tomalak May 17 '10 at 23:23
  • 2
    *(no offense)* [JavaScript Closures for Dummies](http://blog.morrisjohns.com/javascript_closures_for_dummies.html) Example 5 – Felix Kling May 17 '10 at 23:23
  • 1
    It's hard to pick just one for close: http://stackoverflow.com/questions/1734749/ http://stackoverflow.com/questions/643542/ http://stackoverflow.com/questions/1582634/ http://stackoverflow.com/questions/1331769/ http://stackoverflow.com/questions/1552941/ http://stackoverflow.com/questions/750486/ http://stackoverflow.com/questions/933343/ http://stackoverflow.com/questions/1579978/ http://stackoverflow.com/questions/1413916/ http://stackoverflow.com/questions/2808471/ – Christian C. Salvadó May 17 '10 at 23:42
  • 1
    To be fair, the Closure Loop Problem is something that's arguably a language design flaw in JavaScript (and others that provide closures but still use C-style per-function scoping). – bobince May 17 '10 at 23:47

5 Answers5

39

You're sharing the single i variable among all of the callbacks.

Because Javascript closures capture variables by reference, the callbacks will always use the current value of i. Therefore, when jQuery calls the callbacks after the loop executes, i will always be 2.

You need to reference i as the parameter to a separate function.

For example:

function sendRequest(i) {
    $.get('http://www.google.com/', function() {
        alert(i);
    });
}

for (var i = 0; i < 2; i++) {
    sendRequest(i);
}

This way, each callback will have a separate closure with a separate i parameter.

SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
13

Alternative to SLaks' answer

$(function() {
    for (var i=0; i<2; i++) {
        $.get('http://www.google.com/', function(i) {
            return function() { alert(i); }
        }(i));
    }
});
Tomalak
  • 332,285
  • 67
  • 532
  • 628
  • 1
    @RTF No, it's the same thing, just expressed differently. Also, things like this are very likely the last place where your performance goes down the drain, so use it because you like it better, not because you believe it could be faster. (To be absolutely honest, it's not *exactly* the same. It creates one extra function object per loop iteration. Measure the performance difference and find out if you should care.) – Tomalak Jul 08 '13 at 16:00
1

An alternative solution to this is to take your callback and literally make it a named function.

Why would I want to do this?
If a function is doing something where a variable needs to take new scope then it's likely the anonymous function warrants breaking out into a new function. This will also ensure that extra complexity is not introduced to your code by having to copy variables or wrap callbacks. You're code will remain simple and self descriptive.

Example:

function getGoogleAndAlertIfSuccess(attemptNumber) {
    $.get('http://www.google.com/', function() {
        alert(attemptNumber);
    });
}

function testGoogle() {
    for (var i=0; i<2; i++) {
        getGoogleAndAlertIfSuccess(i);
    }
}
Leigh McCulloch
  • 1,886
  • 24
  • 23
1

What's occurring here is your AJAX request $.get is completing after the loop has completed. Because of this, i ends up being the final variable it's set to when the iterations complete, being 2. This is just a weird JavaScript gotcha, and has nothing to do with jQuery.

One thing you can do is queue up these calls asynchronously so that iteration halts until the current AJAX request completes. If you don't want to do that, you can capture the variable i in a function closure in each iteration.

Something like this:

for ( var i = 0; i < 2; i++ )
    (function(iter){
        $.get('http://www.google.com/', function(){
            alert( iter );
        });
    })(i); // Capture i
ground5hark
  • 4,464
  • 8
  • 30
  • 35
0

It appears that you've created a closure inside your loop The Mozilla Developers Reference has a good section about this.

BradBrening
  • 5,418
  • 25
  • 30