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);
});
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
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.
}
}
});