views:

375

answers:

8

In a situation like the code below how would you go about accessing a variable value that is in an anonymous function? I would like to return the bool value of filterData(xmlhttp.responseText, thisForm); which will be boolean to the main checkAvailable function. Thanks in advance.

function checkAvailable(thisForm) {

    var xmlhttp = httpRequest();
    var isValid = true;
    var un = document.getElementById('u_username').value;
    var email = document.getElementById('u_email').value;

    xmlhttp.onreadystatechange = function(isValid) {
     if (xmlhttp.readyState == 4) {
                //I WANT TO ACCESS THIS isValid VARIABLE FROM MAIN FUNCTION checkAvailable
          isValid = filterData(xmlhttp.responseText, thisForm);
     }
    }

    xmlhttp.open("GET","profile_fetch_reg_info.php?do=available&un="+un+"&email="+email+"",true);
    xmlhttp.send(null);

    return isValid;
}

The way i have it now

function validateRegForm(thisForm) {

    var isValid = true;
    var warningIcon = "";//for later in case we want to use an icon next to warning msg

    checkAvailable(thisForm, function(isValid) { });  

    if(isValid == false)
     window.scroll(0,0);

    alert(isValid);

    return false;//isValidForm;
}


function checkAvailable(thisForm, resultFunction) {

     var xmlhttp = httpRequest();
     var un = document.getElementById('u_username').value;
     var email = document.getElementById('u_email').value;

     xmlhttp.onreadystatechange = function(isValid) {
      if(xmlhttp.readyState == 4) {
         isValid = filterData(xmlhttp.responseText, thisForm);
         resultFunction(isValid);
      }
     }
            xmlhttp.open("GET","profile_fetch_reg_info.php?do=available&un="+un+"&email="+email+"",true);
 xmlhttp.send(null);
}
+1  A: 

You need to make your XmlHttpRequest synchronous, you can do this by setting the last parameter of .open() to false, i.e.

xmlhttp.open("GET","profile_fetch_reg_info.php?do=available&un="+un+"&email="+email+"",false);

However this will lock your UI/SJS for the duration of the call

veggerby
Although a valid answer - I don't think you'll want to take this advice - Synchronous calls may be easier to understand - but the callback methods are much safer and don't lock the javascript - dont warn about something taking too long - etc.
gnarf
A: 

If you need to wait for the response to proceed, use SJAX (synchronous javascript and xml) to get the result. Your script will not continue until you have received your response. Beware that timeout errors are not handled well in ie.

On the otherhand, you can use AJAX and do what you need to do inside the readyState check, instead of trying to return the value for something else to handle it.

happytime harry
A: 

You need to pass false as the third parameter to xmlhttp.open(). That will cause the request to be executed synchronously and your program execution will pause until it completes.

If you do this, you won’t need the anonymous function. You can just get xmlhttp.responseText directly:

function checkAvailable(thisForm) {

    var xmlhttp = httpRequest();
    var isValid = true;
    var un = document.getElementById('u_username').value;
    var email = document.getElementById('u_email').value;

    xmlhttp.open("GET", your_parameters, false);
    xmlhttp.send(null);
    isValid = filterData(xmlhttp.responseText, thisForm);

    return isValid;
}

The main drawback to this is that the browser will basically freeze until the xmlhttp.send() call completes.

If you can refactor your code so that the synchronous call is not needed, that would be better IMO.

Nate
Thanks for the suggestion, I tried this and it worked, but I rather used async.
Erik
A: 

Create an object outside the function...

obj = new Object;
obj.isValid = false;
obj.complete = false;

Then your code... except

 obj.isValid = filterData(xmlhttp.responseText, thisForm);
 objt.complete = true;

Make sure object definition is above the function, so that it appears global.

Now on the otherside you can detect if state 4 was achieved, and if it has then you can grab the object value.

You might also need to pass the object through in your function decalarion, it should pass it by ref and update.

JL
A: 

Cheat: make the ajax request handler update data in a hidden element. Then, your polling (I presume) query function can look at the hidden element to see if the "ready" flag was finally set.

Roboprog
-1 for suggesting using a hidden element when a simple variable would do just as well.
Daniel Pryden
Thank you sir, may I have another! Yes, it's dirty, and still global, but I guess I have a bias against (non-local) javascript variables.
Roboprog
+1  A: 

In general terms, the way to return a value from an anonymous function would be to make use of the lexical scoping nature of Javascript. To do this, you would need to declare a variable in the same scope as the anonymous function and have the function set the variable during its execution.

For example:

function a() {
    var x = 1;
    (function() { x = 2; })();
    alert(x); // x will be 2
}

However, this is all predicated on the fact that the execution is linear, meaning that the the alert happens after the anonymous function is executed. In the code you presented above, this wouldn't happen because the XMLHttpRequest is asynchronous meaning that the onreadystatechange callback will be called at some other point in time. You could change your XMLHttpRequest to be synchronous but this would lock up the UI of your page while the request is on progress.

function checkAvailable(thisForm) {

    var xmlhttp = httpRequest();
    var isValid = true;
    var un = document.getElementById('u_username').value;
    var email = document.getElementById('u_email').value;

    xmlhttp.open("GET","profile_fetch_reg_info.php?do=available&un="+un+"&email="+email+"",false);
    xmlhttp.send(null);

    isValid = filterData(xmlhttp.responseText, thisForm);

    return isValid;
}

The best way to work with this sort of situation is to move to a completely asynchronous model. In this model, your checkAvailble() function would be restructured so that it takes a callback that is invoked after the validity is determined. Below is an example of what this might look like:

function whenAvailable(theForm, callback) {
    var xmlhttp = httpRequest();
    var un = document.getElementById('u_username').value;
    var email = document.getElementById('u_email').value;

    xmlhttp.onreadystatechange = function() {
        if (xmlhttp.readyState === 4) {
            if (callback) {
                var isValid = filterData(xmlhttp.responseText, thisForm);
                callback.call(null, isValid);
            }
        }         
    }

    xmlhttp.open("GET","profile_fetch_reg_info.php?do=available&un="+un+"&email="+email+"",true);
    xmlhttp.send(null);

}

A call to this function would look like the following:

whenAvailable(document.getElementById('someForm'), function(valid) {
    if (valid) {
        // do something when valid
    } else {
        // do soemthing when invalid
    }
});
Bryan Kyle
+5  A: 

Modify the checkAvailable function to take an additional parameter which is the function to call with the result.

function checkAvailable(thisForm, resultFunction) {
  ..
  xmlhttp.onreadystatechange = function(isValid) {
    if (xmlhttp.readyState == 4) {
       //I WANT TO ACCESS THIS isValid VARIABLE FROM MAIN FUNCTION checkAvailable
       isValid = filterData(xmlhttp.responseText, thisForm);
       resultFunction(isValid);
    }
  }
}

Then, you can call it something like this:

checkAvailable(thisForm, function(isValid) { 
  // Use the isValid value which is the result of the checkAvailable call.
});

EDIT Here is a change to the modified code you posted.

function validateRegForm(thisForm) {
  var isValid = true;
  var warningIcon = "";//for later in case we want to use an icon next to warning msg

  checkAvailable(thisForm, function(isValid) { 
    if(isValid == false)
      window.scroll(0,0);

    alert(isValid);
  }

  // WARNING!!  This will happen before the result is discovered.
  // You'll need to modify the function that called into validateRegForm.
  // It should not wait for a return parameter either.
  return false;//isValidForm;
}
John Fisher
fast fingers Mr. Fisher ;) - deleting my duped answer and adding a note to yours: Any asynchronous calls should be handled with callbacks instead of return values
gnarf
+1 for using a callback instead of synchronous I/O, and also for doing it correctly. Both gnarf and jvenema's answers put the resultFunction call outside the if() block, which is probably not what you want. You probably only want to call the callback when xmlhttp.readyState == 4, because otherwise you will get spurious callbacks for other readyState changes. (In Gecko, you can get an arbitrarily large number of callbacks if you have a large or slow-loading response, IIRC.)
Daniel Pryden
I actually edited mine to put the resultFunc call inside the if before I noticed John's answer beat me to it anyway, only one note the funciton(isValid) is unnecessarily scoping away from the var isValid defined above it. should just be function() {
gnarf
I'm trying to understand this, but I am confused. So checkAvailable shouldn't have a RETURN? Also I'm confused with the second part there, you said "use the isValid value", but how? Sorry for being such a n00b. =/
Erik
Right. CheckAvailable doesn't need to return anything. Instead, the part of your code that waits for the result will be inside an anonymous function that executes once checkAvailable finishes.
John Fisher
hmmm...i tried it, but it's not working. i updated my posting, if you can take a quick look.
Erik
I edited the answer with a fix and a warning about the changes you made.
John Fisher
A: 

The way you're accessing it within the scoped function is fine, the problem is what you're trying to do doesn't work the way you're expecting it to. As others have mentioned, you could go with a synchronous request, however that's probably not what you really want (since it'll hang your UI).

Looking at your code, I assume you have a function that needs to know whether or not your request was successful, and then continue processing. So, instead of using a flag, you need to have a callback that can execute one completion of the ajax request. Something like the following:

function checkAvailable(thisForm, callback) {

var xmlhttp = httpRequest();
var isValid = true;
var un = document.getElementById('u_username').value;
var email = document.getElementById('u_email').value;

xmlhttp.onreadystatechange = function(isValid) {
    if (xmlhttp.readyState == 4) {
            //I WANT TO ACCESS THIS isValid VARIABLE FROM MAIN FUNCTION checkAvailable
            isValid = filterData(xmlhttp.responseText, thisForm);
    }
    callback(isValid);
}

xmlhttp.open("GET","profile_fetch_reg_info.php?do=available&un="+un+"&email="+email+"",true);
xmlhttp.send(null);

}

jvenema