1

I need an AJAX API call to complete before it's containing function does. All the solutions I have looked up use jQuery, which I do not want to add as a dependency just to make a single REST call...

I have been reading the following question How do I return the response from an asynchronous call? and am still attempting to make it work. Again, I do not want to add jQuery as a dependency.

Right now if a failing zip code value is entered, when the user clicks Submit an alert will show the other failing business rules without the "Mismatch in State and Zip." error. When logging to console I see that the REST call is made and a successful response received, but it appears alert(error) is called before the async REST call has completed and appended the value of error.

JS

function checkForm(form){
  var state = form.state;
  var zip = form.zip;
  var error = '';
  var flag = 0;

  var ckZip = checkZip(state, zip);
  if (ckZip.flag == 1){
    flag = 1;
  }
  error += ckZip.error;

  // show error and disable form submit if flag == 1
  if (flag == 1){
    alert(error);
    return false;
  }
}

function checkZip(state, zip){
  var state = form.state;
  var zip = form.zip;
  var error = '';
  var flag = 0;

  // check if value entered
  if (zip == ''){
    error += "Please provide a zip/postal code.\r\n";
    flag = 1;
  }

  // business rule: cannot enter - in a ZIP
  if (zip.search(/\x2d/) != -1){
    error += "Please remove dash from zip or postal code.\r\n";
    flag = 1;
  }

  // check if state and zip match
  // IMPORTANT: Fill in your client key
  var clientKey = "this-is-a-valid-key";
  var zipcode = zip.substring(0, 5);
  var url = "https://www.zipcodeapi.com/rest/"+clientKey+"/info.json/" + zipcode + "/radians";
  // Make AJAX request
  var request = new XMLHttpRequest();
  request.open('GET', url, true);

  request.onreadystatechange = function() {
    if (this.readyState === 4) {
      if (this.status >= 200 && this.status < 400) {alert("Got it");
        // Success!
        var data = JSON.parse(this.responseText);alert(data.state);
        if (data.state != state){
          error += "Mismatch in State and Zip.\r\n";
      flag = 1;
        }
      } else {
        // Error :(
        var response = JSON.parse(this.responseText);
        error += "error: " + response.error_msg;
      }
    }
  };

  request.send();
  request = null;

  return {flag: flag, error: error};
}

HTML

<form>
  <select id="state" name="state">
      <option value="" selected=""></option>
      <option value="AA">APO AA</option>                
      <option value="AE">APO AE</option>                
      <option value="AP">APO AP</option>                
      <option value="AL">Alabama</option>               
      <option value="AK">Alaska</option>                
      <option value="AZ">Arizona</option>               
      <option value="AR">Arkansas</option>                
      <option value="CA">California</option>                
      <option value="CO">Colorado</option>                
      <option value="CT">Connecticut</option> 
      <!-- ... -->              
  </select>

  <input type="text" id="zip" name="zip" value="">   
  <input type="Submit" id="Submit" name="Submit" value="Continue" onclick="return checkForm(this.form)">
</form>
Community
  • 1
  • 1
mhatch
  • 4,441
  • 6
  • 36
  • 62
  • I have read the question marked as duplicate and I appreciate the responses and help, however, I am still struggling to make it work. I do not want to use a sync call. I'm trying to figure out how to use a callback. Your help and patience is appreciated. – mhatch Jun 23 '16 at 14:52

2 Answers2

0

The only way to make your function actually return the information from the ajax call is to make the ajax call synchronous. I strongly recommend that you don't do that. It locks up the UI of the browser tab (at least) during the call, making for a poor user experience, and moreover there's no need.

Instead, embrace the asynchronous call and learn how to deal with the fact that the function cannot return the information as a return value; see this question and its answers for how to do that. It's really not as difficult as people sometimes think. :-)

Here's how you can make your checkZip work asynchronously:

First, move the call from the submit button to the form itself:

<form onsubmit="return checkZip(this)">

Now, make checkZip always prevent form submission by returning false and have it submit the form when the ajax is done:

function checkZip(form) {

    // ...

    request.onreadystatechange = function() {
        // ...
        if (/* it's okay to submit the form*/) {
            form.submit(); // Does NOT call the handler again
        } else {
            // show error to user
        }
    };

    // Stop the initial form submission
    return false;
}

Note: The form will not contain the Submit field with the value Continue, since when it was really submitted, that button wasn't pressed. If you need it to, remove the name from the submit button and instead include a hidden field:

<input type="hidden" name="Submit" value="Continue">

But just for completeness, it is possible to make the ajax call synchronous: The third argument to open is a flag where true = asynchronous and false = synchronous, so:

request.open('GET', url, false);
// ----------------------^^^^^

But again, I strongly recommend that you don't do that.

Community
  • 1
  • 1
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Thanks, I'll take a look. I have been reading the other SO questions, but have not been able to create a successful solution yet. – mhatch Jun 23 '16 at 14:45
  • @mhatch: For this specific scenario, it's actually fairly straightforward -- I've added it to the answer. – T.J. Crowder Jun 23 '16 at 14:56
  • @TJ I suppose I should have mentioned, I simplified the scenerio a bit. The form submit actually calls a different function (not checkZip) which calls checkZip as well as other functions. – mhatch Jun 23 '16 at 14:59
  • @mhatch: It's still the same concept though. `checkForm` cancels the default submit and passes a function to `checkZip`. `checkZip` calls that function (a callback) when it knows the answer. `checkForm` submits the form. And if you have multiple things like `checkZip` that you need to call, that's where *promises* get really useful. They're covered in the linked question's answers. – T.J. Crowder Jun 23 '16 at 15:07
  • What about a case where the country is different and a zip is not required? – mhatch Jun 23 '16 at 15:08
  • @mhatch: What about it? At that point, it's just business logic. I've demonstrated the technique as applied to your scenario, and the link goes into the concepts in detail with many more examples, etc. I think that's plenty to work with. – T.J. Crowder Jun 23 '16 at 15:10
-2

You could either make your XMLHTTPRequest synchronous:

var request = new XMLHttpRequest();
request.open('GET', url, false);

Although as others have pointed out this is not recommended.

Another option would be to wrap your alert in a timeout that defers execution for some small amount of time, enough to give your request time to process:

setTimeout(function() { 
    // show error and disable form submit if flag == 1
    if (flag == 1){
       alert(error);
       return false;
   } 
}, 1000);     
Daniel A. Thompson
  • 1,904
  • 1
  • 17
  • 26