ajaxjquery-deferredgoogle-geocoderpanoramio

wait for all the ajax request to panoramio api inside google geocoder to finish and then perform something


I am developing a mobile application that knows certain interests of a person, like cities for example. So the application knows New York for example.

I want to search for new york photos in panoramio api but for that i have to know the coordinates of New York City. so i do the following:

geocoder.geocode( { 'address': places[j].name}, function(results, status) {
    if (status == google.maps.GeocoderStatus.OK){
        var request = $.ajax({type:"GET",
                             url: "http://www.panoramio.com/map/get_panoramas.php?set=public&from=0&to=20&minx="+ (results[0].geometry.location.lng()-distance) +"&miny="+ (results[0].geometry.location.lat()-distance)+"&maxx="+ (results[0].geometry.location.lng()+distance) +"&maxy="+ (results[0].geometry.location.lat()+distance) +"&size=medium&mapfilter=true",
                             dataType: "jsonp",
                             success : function(){
                                   if(typeof places[j] !== "undefined")
                                        succeeded.push(places[j].name);
                                   }
                             });
                            requestPlaces.push(request);
    }
});

this is inside a for loop that loops for the amount of interests a certain person has. places[j].name is the name of an interest. distance is the treshold from the are where i want the pictures to be from. So far so good.

Then i want to gather all the information into the requestPlaces array. requestPlaces is declared as follows:

var requestPlaces = [];

and i push all the performed requests into that array so later i can do:

$.when.apply(null, requestPlaces).done(function () {
    console.log("tamanho do request places: dentro do when apply" + requestPlaces.length);
    console.log(arguments);

});

and here is the problem. inside the $.when.apply(null, requestPlaces) i am getting size 0 and arguments to be an empty array. It seems that it is not waiting for all the requests to be done and pushed to the array of requestPlaces. What can i do to solve this?

Several considerations:

this block of code is inside another $.when.apply block for another request to get the persons interests, which is working.

and also i know as a fact that the request i am doing to panoramio api is returning successfull results and has nothing to do with entering in the $.when.apply(null, requestPlaces) first.

Full code for betterunderstanding if needed:

for(var j = 0; j<places.length; j++){
    geocoder.geocode( { 'address': places[j].name}, function(results, status) {
            if (status == google.maps.GeocoderStatus.OK){

                var request = $.ajax({
                        type:"GET",
                        url: "http://www.panoramio.com/map/get_panoramas.php?set=public&from=0&to=20&minx="+ (results[0].geometry.location.lng()-distance) +"&miny="+ (results[0].geometry.location.lat()-distance) +"&maxx="+ (results[0].geometry.location.lng()+distance) +"&maxy="+ (results[0].geometry.location.lat()+distance) +"&size=medium&mapfilter=true",
                        dataType: "jsonp",
                        success : function(){
                             if(typeof places[j] !== "undefined")
                                 succeeded.push(places[j].name);
                        }
                });
                requestPlaces.push(request);
        }
    });

}

$.when.apply(null, requestPlaces).done(function () {
        console.log("size" + requestPlaces.length);
        console.log(arguments);
});

Solution

  • João, your problem arises because geocoder.geocode(...) and $.ajax(...) are performed in asynchronous series. The array requestPlaces correctly accumulates the $.ajax(...) promises but it only does so after the outer geocoder.geocode(...) requests and their corresponding $.ajax(...) requests return. Therefore, at the point where the for loop finishes, requestPlaces is guaranteed still to be empty - all the geocoder.geocode(...) requests will have been made but their $.ajax(...) requests are guaranteed not to have been sent yet, let alone to have returned.

    The solution is actually quite simple; populate the array requestPlaces with composite promises, each one being the promise returned by (in pseudocode) geocode(...).then($.ajax(...)).

    Trying to do everything inside the master routine is rather confusing. It becomes much clearer with the async tasks separated out as utility functions, as follows :

    function doGeocode(placeName) {
        return $.Deferred(function(dfrd) {
            geocoder.geocode({ 'address': placeName }, dfrd.resolve);
        }).promise();
    }
    
    function doPanoramio(loc) {
        return $.ajax({
            url: "http://www.panoramio.com/map/get_panoramas.php",
            type: "GET",
            data: {
                'set: 'public',
                'from': 0,
                'to': 20,
                'minx': loc.lng() - distance,
                'miny': loc.lat() - distance,
                'maxx': loc.lng() + distance,
                'maxy': loc.lat() + distance,
                'size': 'medium',
                'mapfilter': 'true'
            }
            dataType: "jsonp"
        });
    }
    

    Note that both these utilities return promises.

    And a third utility to help provide meaningful geocode error messages :

    function inverseLookup(obj, value, k) {
        // Find an object's key from a given value.
        $.each(obj, function(key, val) {
            return (val !== value) ? true : !(k = key);//continue or break
        });
        return k || '';
    }
    

    Now, the simplified master routine is best phrased as a .map of places, returning the required array of promises, as follows :

    var requestPlaces = $.map(places, function(place) {
        if(place.name) {
            return doGeocode(place.name).then(function(results, status) {
                if (status == google.maps.GeocoderStatus.OK) {
                    return doPanoramio(results[0].geometry.location).then(null, function(xhr, textStatus) {
                        return $.when(place.name + ': ' + ': panoramio failure : ' + textStatus);
                    });
                } else {
                    return $.when(place.name + ': ' + ': geocode failure : ' + inverseLookup(google.maps.GeocoderStatus, status, '?'));
                }
            });
        } else {
            return null;
        }
    });
    

    Note that failure cases could be passed down the chain as such. However, you don't really want any individual geocode/panoramio failure (including "ZERO_RESULTS") to scupper the whole $.when() enterprise in the master routine. Passing on failures as (detectable) success is a simple workaround for jQuery's lack of an $.allSettled() method.

    And the $.when.apply(...) will be something like this :

    console.log("size: " + requestPlaces.length);
    
    $.when.apply(null, requestPlaces).then(function () {
        console.log("All geocoding and panoramio requests complete");
        $.each(arguments, function(i, result) {
            if($.isPlainObject(result)) {
                //result is a Panoramio response - process the result.photos array as required.
            } else {
                console.error(result);//if result is not a plain object, then it's an error message.
            }
        });
    });
    

    untested - may need debugging

    EDIT

    You probably need to test for result.photos instead of $.isPlainObject(result). The following version will also run through the results backwards, processing every second result and ignoring the others.

    $.each(Array.prototype.slice.apply(arguments).reverse(), function(i, result) {
        if((i % 2) === 0) {
            if(result.photos) {
                //result is a Panoramio response - process the result.photos array as required.
            } else {
                console.error(result);//if result is not a plain object, then it's an error message.
            }
        }
    });