Ali Bassam
Ali Bassam

Reputation: 9969

Cannot access a variable in a function within a function in JavaScript

function updateRoomsList() {
    //empty the rooms list
    $("#rooms").empty();
    //fetch the non-joined rooms, id and name
    $.get('JoinPart', {
        goal: 5,
        userName: $("#userName").html()
    }, function (responseText) {
        var i = 0;
        id = "";
        while (i < responseText.length) {
            if (responseText.charAt(i) == '.') {
                //Now we got the full Id of a room, lets add it
                $.get('JoinPart', {
                    goal: 6,
                    roomId: id
                }, function (responseText) {
                    roomName = responseText;
                    $("#rooms").append('<div id="room' + id + '" class="listItem"><span title="Join Room" class="joinButton">+</span><div class="listItemContent">' + roomName + '</div></div>');
                });
                id = "";
            } else {
                id = id + responseText.charAt(i);
            }
            i++;
        }
    });
}

In this Function there's a variable id, if I alert(id); after

if(responseText.charAt(i)=='.')

I get the proper value of the id calculated in the else , but when I do alert(id); inside the $.get the id is empty "" , means in the append function, the id has no value, how can I get this id to have the value outside of the $.get function ?

Upvotes: 1

Views: 132

Answers (2)

Raffaele
Raffaele

Reputation: 20885

That's because id is scoped in the outer closure, so your inner callback gets a reference to the last value it's set at the end of the while loop. If you need a private copy of the id variable inside your second $.get() callback, you need to write

$.get('JoinPart', {goal :6, roomId :id }, (function (id) {
  return function (responseText) {
    roomName = responseText;
    $("#rooms").append('<div>' + id + '</div>');
  }
})(id));

When you do this, you create a new scope with a local variable id which is given the exact value id had at the current iteration, and not the local variable id itself, which at the end of the loop (or really just at the next iteration) could be anything.

The construct (function(arg){})(arg) is called an immediately invoked function expression. Please note that this function returns a new function object, the one required by $.get()

A closure is a function defined inside another one. A closure, by definition, can access all variables local to the outer function. When you supply a callback for later execution to $.get(), you are in fact using a closure: when the callback will be executed (possibly after several seconds) the JS engine will make id still accessible, but its value obviously depends on the rest of the code.

If you need to store the current snapshot of a variable for later reading, you need to create a new closure (ie a new scope): we use the IIFE for this purpose. The IIFE is executed right before the actual HTTP request is fired (ie before the $.get() is even called), and it's the nearest scope for the callback, so it will be used to resolve the id reference when the callback will be executed.

You can test if you understand the concept by figuring out what the following snippet will print

function inner() {
  console.log(i);          
}

for (var i = 0; i < 3; i++) {
  inner();
  setTimeout(inner, 1000);
}​

Upvotes: 1

djechlin
djechlin

Reputation: 60848

Code flow is not the order you think.

            id="";

Is happening before the function-within-a-function, which is called a closure, by the way (this should help in your googling). Add the print statements

alert("here 1");
alert("here 2"); 

in the closure, and before the id=""; and this should jump out.

Easiest fix is probably to swap id with roomId in the closure, since, being a simple string or int, the value is fully copied to roomId, where it will be safe even if id changes (ok, more accurately, id=... only changes the reference so the value referred to by roomId stays the same.)

Upvotes: 1

Related Questions