Wayne Molina
Wayne Molina

Reputation: 19636

Returning a value from a jQuery Ajax method

I'm trying to use Javascript in an OO style, and one method needs to make a remote call to get some data so a webpage can work with it. I've created a Javascript class to encapsulate the data retrieval so I can re-use the logic elsewhere, like so:

 AddressRetriever = function() { 
   AddressRetriever.prototype.find = function(zip) { 
       var addressList = [];
       $.ajax({ 
           /* setup stuff */
           success: function(response) { 
               var data = $.parseJSON(response.value);
               for (var i = 0; i < data.length; i++) { 
                   var city = data[i].City; // "City" column of DataTable
                   var state = data[i].State; // "State" column of DataTable
                   var address = new PostalAddress(postalCode, city, state); // This is a custom JavaScript class with simple getters, a DTO basically.
                   addressList.push(address);
               }
           }
       });
       return addressList;
   }
 }

The webpage itself calls this like follows:

$('#txtZip').blur(function() { 
    var retriever = new AddressRetriever();
    var addresses = retriever.find($(this).val());

    if (addresses.length > 0) { 
        $('#txtCity').val(addresses[0].getCity());
        $('#txtState').val(addresses[0].getState());
    }
});

The problem is that sometimes addresses is inexplicably empty (i.e. length = 0). In Firebug the XHR tab shows a response coming back with the expected data, and if I set an alert inside of the success method the length is correct, but outside of that method when I try to return the value, it's sometimes (but not always) empty and my textbox doesn't get populated. Sometimes it shows up as empty but the textbox gets populated properly anyways.

I know I could do this by getting rid of the separate class and stuffing the whole ajax call into the event handler, but I'm looking for a way to do this correctly so the function can be reused if necessary. Any thoughts?

Upvotes: 2

Views: 747

Answers (4)

jfriend00
jfriend00

Reputation: 708156

In a nutshell, you can't do it the way you're trying to do it with asynchronous ajax calls.

Ajax methods usually run asynchronous. Therefore, when the ajax function call itself returns (where you have return addressList in your code), the actual ajax networking has not yet completed and the results are not yet known.

Instead, you need to rework how the flow of your code works and deal with the results of the ajax call ONLY in the success handler or in functions you call from the success handler. Only when the success handler is called has the ajax networking completed and provided a result.

In a nutshell, you can't do normal procedural programming when using asynchronous ajax calls. You have to change the way your code is structured and flows. It does complicate things, but the user experience benefits to using asynchronous ajax calls are huge (the browser doesn't lock up during a networking operation).

Here's how you could restructure your code while still keeping the AddressRetriever.find() method fairly generic using a callback function:

AddressRetriever = function() { 
   AddressRetriever.prototype.find = function(zip, callback) { 
       $.ajax({ 
           /* setup stuff */
           success: function(response) { 
               var addressList = [];
               var data = $.parseJSON(response.value);
               for (var i = 0; i < data.length; i++) { 
                   var city = data[i].City; // "City" column of DataTable
                   var state = data[i].State; // "State" column of DataTable
                   var address = new PostalAddress(postalCode, city, state); // This is a custom JavaScript class with simple getters, a DTO basically.
                   addressList.push(address);
               }
               callback(addressList);
           }
       });
   }
 }

$('#txtZip').blur(function() { 
    var retriever = new AddressRetriever();
    retriever.find($(this).val(), function(addresses) {
        if (addresses.length > 0) { 
            $('#txtCity').val(addresses[0].getCity());
            $('#txtState').val(addresses[0].getState());
        }
    });

});

Upvotes: 2

redDevil
redDevil

Reputation: 1919

    AddressRetriever = function() { 
   AddressRetriever.prototype.find = function(zip) { 
       var addressList = [];
       $.ajax({ 
           /* setup stuff */
           success: function(response) { 
               var data = $.parseJSON(response.value);
               for (var i = 0; i < data.length; i++) { 
                   var city = data[i].City; // "City" column of DataTable
                   var state = data[i].State; // "State" column of DataTable
                   var address = new PostalAddress(postalCode, city, state); // This is a custom JavaScript class with simple getters, a DTO basically.
                   addressList.push(address);
                   processAddresss(addressList);
               }
           }
       });
   }
 }

function processAddresss(addressList){
     if (addresses.length > 0) { 
        $('#txtCity').val(addresses[0].getCity());
        $('#txtState').val(addresses[0].getState());
    }
}

or if you want don't want to make another function call, make the ajax call synchronous. Right now, it is returning the array before the data is pushed into the array

Upvotes: 2

Madara&#39;s Ghost
Madara&#39;s Ghost

Reputation: 175098

AJAX calls are asynchroneous, which means they don't run with the regular flow of the program. When you execute

if (addresses.length > 0) { 

addresses is in fact, empty, as the program did not wait for the AJAX call to complete.

Upvotes: 0

Dave Newton
Dave Newton

Reputation: 160321

Not inexplicable at all, the list won't be filled until an indeterminate amount of time in the future.

The canonical approach is to do the work in your success handler, perhaps by passing in your own callback. You may also use jQuery's .when.

Upvotes: 0

Related Questions