Google Maps API delay in loading variable - javascript

I am trying to use the Google Maps API to fetch the city name from a zipcode. This is not my strength (I'm more of a PHP person), so I am using sample code I found, with some modifications suggested by a friend.
The problem is, after I call the function my global variable with the city name is still at it's initialized value of null. If, however, I do an alert with this value, the rest of the processing suddenly has the correct value loaded! I tried putting in a time delay to see if Google was just slow in returning the value, but it makes no difference.
Here's the function:
var geocoder = new google.maps.Geocoder();
function getGoogleAddress(zipcode) {
//var gcity = "N/A"; switch to using global var defined above
geocoder.geocode( { 'address': zipcode}, function (result, status) {
for (var component in result[0]['address_components']) {
for (var i in result[0]['address_components'][component]['types']) {
if (result[0]['address_components'][component]['types'][i] == "locality") {
gcity = result[0]['address_components'][component]['short_name'];
break;
}
}
}
});
}
And this is where it's called from...including the alert and the pause:
gcity="";
getGoogleAddress(form.zip.value);
var holdcity = gcity;
var date = new Date();
var curDate = null;
do { curDate = new Date(); }
while(curDate-date < 2000);
alert(gcity);
As I said, the alert returns null, but the rest of the processing has the proper city name in gcity. If I leave out the alert, the rest of the processing fails because gcity is null.
Any advice or suggestions are greatly appreciated. Thanks.

Asynchronous.
The function (result, status) { is only executed when Google's servers have responded. The rest of your getGoogleAddress function doesn't wait for that, but exits, and Javascript continues execution at var holdcity = gcity.
The reason it works after the alert, is that by then, Google will have responded, and the gcity variable will have been executed.
Possible solution:
var geocoder = new google.maps.Geocoder();
function getGoogleAddress(zipcode, successFunction) {
//var gcity = "N/A"; switch to using global var defined above
geocoder.geocode( { 'address': zipcode}, function (result, status) {
for (var component in result[0]['address_components']) {
for (var i in result[0]['address_components'][component]['types']) {
if (result[0]['address_components'][component]['types'][i] == "locality") {
var gcity = result[0]['address_components'][component]['short_name'];
successFunction(gcity);
break;
}
}
}
});
}
And this is where it's called from...including the alert and the pause:
getGoogleAddress(form.zip.value, function (holdcity) {
var date = new Date();
var curDate = null;
do { curDate = new Date(); }
while(curDate-date < 2000);
alert(holdcity);
});

Related

Javascript / API Blank Resolved Promise and Non Global Data from Request

I'm trying to set then grab a session storage variable that is returned from an API I'm using. The API is returning the proper values, and these values are accessible within the function which returns the result and its status, but not outside of the request. The request:
var geom;
var latArray = new Array();
var longArray = new Array();
function getLatLong(placeIdent) {
service = new google.maps.places.PlacesService(document.createElement('div'));
request = {placeId: ""+placeIdent+""};
service.getDetails(request, function(result, status) {
if (status !== google.maps.places.PlacesServiceStatus.OK) {
console.log(status);
} else {
writeLatLongData(result);
while(geom != "") {
lat = geom.lat();
latArray.push(lat);
long = geom.lng();
latArray.push(long);
console.log(geom);
console.log(lat);
console.log(long);
timeout();
}
}
}
)
}
function writeLatLongData(result) {
geom = result.geometry.location;
return geom;
}
I know this function is the source of the problem, but if it's any help I'm attempting to access the stored values like so:
function timeoutOne() {
setTimeout(function(){
getGeometry(placeArray);
}, 1000);
}
timeoutOne();
function getGeometry(placeArray) {
for (var j=0; j < nightsArray.length; j++) {
getLatLong(placeArray[j]);
}
}
I'm pretty sure it's an async issue, but I'm not that familiar with Async/await and everything I've tried just returns an empty value when the promise is resolved (perhaps because the data are not accessible outside of the request function, still).
Does anyone have any suggestions?
What if below condition gets true,
status !== google.maps.places.PlacesServiceStatus.OK
your google places api call is getting failed (may be because you are making too much calls per 10 seconds), if this happens nothing will be returned which in turn making function getLatLong return undefined.
Check if at least once this function returning something.

Gecoding addresses through Google maps api and deferring the results

I am using Google's maps API to geocode two addresses. I defer the returned results and use a $.when().then() method to execute my logic once I get the coordinates for the string addresses. The problem is the API always returns the result as resolved, even if there is an error. For example if there is no internet connection instead of request timing out I get the status as ERROR and result as null or if I enter an invalid address I get the status ZERO_RESULTS and result an empty array []. As I am only interested in getting the proper coordinate results I want to handle all other responses as a geocoding error, which I don't know how to do. You can also see that I have to check if input fields are empty before geocoding because of the same problem.
I am just getting acquainted to asynchronous flow and need some guidance.
I am using jquery 1.9.2 and google maps APIv3. (I can hard code all the conditions but I want to improve my coding skills and have something more generic. Is that possible.)
I will give my code here as well.
function geocode(addString) {
var deferred = $.Deferred();
var geocoder = new google.maps.Geocoder();
var request = {
address: addString
};
geocoder.geocode(request, function(results, status) {
if (status === google.maps.GeocoderStatus.OK) {
deferred.resolve(results[0].geometry.location);
}
else {
// no idea what to do here
}
});
return deferred;
}
function Options() {
var origin = $("#origin-field").val();
var destination = $("#destination-field").val();
if (origin != "" && destination != ""){
var originCoords = geocode(origin);
var destinationCoords = geocode(destination);
$.when(originCoords, destinationCoords)
.then(function(originCoordinates, destinationCoordinates) {
console.log(originCoordinates.lat() + ',' + originCoordinates.lng());
console.log(destinationCoordinates.lat() + ',' + destinationCoordinates.lng());
}, function() {
toastMessage("Geo-coding error");
});
}
else {
toastMessage("Origin and/or Destination missing");
}
}
I solved my problem thanks to this example. As I said I just started looking at asynchronous flow so didn't know how to solve this simple problem.
What I did is just catch all non-OK statuses in an else block and passed it to the deferred.reject() method. So my code became like this.
function geocode(addString) {
var deferred = $.Deferred();
var geocoder = new google.maps.Geocoder();
var request = {
address: addString
};
geocoder.geocode(request, function(results, status) {
if (status === google.maps.GeocoderStatus.OK) {
deferred.resolve(results[0].geometry.location);
}
else {
deferred.reject(status);
}
});
return deferred;
}
function Options() {
var origin = $("#origin-field").val();
var destination = $("#destination-field").val();
var originCoords = geocode(origin);
var destinationCoords = geocode(destination);
$.when(originCoords, destinationCoords)
.then(function(origin, destination) {
//some logic in case of success
}, function(status) {
toastMessage("Geo-coding error:" + status);
});
}

Return value with google maps geocoder [duplicate]

This question already has answers here:
How do I return a variable from Google Maps JavaScript geocoder callback?
(5 answers)
Closed 9 years ago.
I just can't find what's wrong with this bit of code:
function getLocationName(latitude, longitude) {
if (isNaN(parseFloat(latitude)) || isNaN(parseFloat(longitude))) {
return false;
}
var locationName;
var geocoder = new google.maps.Geocoder();
var latlng = new google.maps.LatLng(latitude, longitude)
// Reverse Geocoding using google maps api.
geocoder.geocode({ 'latLng': latlng }, function (results, status) {
if (status == google.maps.GeocoderStatus.OK) {
if (results[1]) {
locationName = results[1].formatted_address;
alert(locationName);
}
else {
locationName = "Unknown";
}
}
else {
locationName = "Couldn't find location. Error code: " + status;
}
});
alert(locationName);
return locationName;
}
I call this from a jquery event handler like this:
$("#id").on("event", function (event, ui) {
$("#userLocation").text(getLocationName(latitude, longitude));
});
Weird part is that the first alert gets the correct value of 'locationName' but the second one always returns 'undefined'. I tried initializing the variable with a value and in that case the first alert again returned the correct location name but the second one returned the initialization value. This gives me a notion that this might be a variable scope related problem but I just can't figure what.
PS. I don't have any other variables (local/global) by the same name.
Update: The alert works fine now (thanks to Lwyrn's answer) but the return value is still wrong. I've followed the answers in the linked SO question and still I couldn't 'return' the right value. The alert did work fine.
You have to move "alert(locationName);" into the geocoder.geocode callback. Because geocoder.geocode executes an AJAX request. When you throw the alert the var locationName is still undefined (not set).
Try it like this
function getLocationName(latitude, longitude, callback) {
if (isNaN(parseFloat(latitude)) || isNaN(parseFloat(longitude))) {
return false;
}
var locationName;
var geocoder = new google.maps.Geocoder();
var latlng = new google.maps.LatLng(latitude, longitude)
// Reverse Geocoding using google maps api.
geocoder.geocode({ 'latLng': latlng }, function (results, status) {
if (status == google.maps.GeocoderStatus.OK) {
if (results[1]) {
locationName = results[1].formatted_address;
alert(locationName);
}
else {
locationName = "Unknown";
}
}
else {
locationName = "Couldn't find location. Error code: " + status;
}
alert(locationName);
callback(locationName);
});
}
To get the "return" you have to create a your own callback.
Try like this
$("#id").on("event", function (event, ui) {
getLocationName(latitude, longitude, function(result){
$("#userLocation").text(result);
});
});
As for the alert, the return is called before the ajax request. So you have to use a callback to be called when the ajax request has done his job!

Returning values in Javascript

I have a (hopefully quite simple) Javascript problem. I've search but found nothing that is really relevant to the problem.
Basically I have a function (addToGlobe) that calls two other functions (codeAddressLat and codeAddressLng) as it runs. The two called functions should both return a float value to the first function, which then uses them. The subfunctions definitely work correctly - I did a print statement to check that the "numfinal" variable in each has a value, and it does.
However, when I add print statements to the calling function (as commented in the code), it returns 'undefined'. Therefore, the problem seems to be when the numfinal value is returned.
Thanks :)
function addToGlobe(uname, uid, pmcity) {
// Get lat & long of city
var pmlat = codeAddressLat(pmcity);
var pmlong = codeAddressLng(pmcity);
log(pmlat); // PROBLEM! Prints 'undefined'
log(pmlong); // PROBLEM! Prints 'undefined'
// Rest of function removed to keep it simple
}
function codeAddressLat(inputcity) {
geocoder = new google.maps.Geocoder();
var latlng = new google.maps.LatLng(-34.397, 150.644);
geocoder.geocode( { 'address': inputcity}, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
var llsplit = new Array();
bkresult = String(results[0].geometry.location);
bkresult = bkresult.replace(/[\(\)]/g, "");
llsplit = bkresult.split(',');
numfinal = parseFloat(llsplit[0]);
return numfinal;
} else {
log('<b><font color="#C40031">Geocode was not successful:</b> ' + status);
}
});
}
function codeAddressLng(inputcity) {
// Basically the same function as above. Removed for simplicity
}
codeAddressLat is not actually returning anything. The anonymous function it passes to geocoder.geocode is.
Since geocoder.geocode is running asynchronously, codeAddressLat can't wait around for its answer. So codeAddressLat really can't return anything of value. Instead codeAddressLat needs to become asynchronous too. This is a common pattern in JavaScript.
function addToGlobe(uname, uid, pmcity) {
codeAddressLat(pmcity, function(pmlat) {
// do something with pmlat
});
...
}
function codeAddressLat(inputcity, callback) {
geocoder = new google.maps.Geocoder();
var latlng = new google.maps.LatLng(-34.397, 150.644);
geocoder.geocode( { 'address': inputcity}, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
var llsplit = new Array();
bkresult = String(results[0].geometry.location);
bkresult = bkresult.replace(/[\(\)]/g, "");
llsplit = bkresult.split(',');
numfinal = parseFloat(llsplit[0]);
// instead of returning, call the callback with the result
callback(numfinal);
} else {
log('<b><font color="#C40031">Geocode was not successful:</b> ' + status);
}
});
}
You don't have a return statement in codeAddressLat, you have one inside a callback function defined inside codeAddressLat.
Your function codeAddressLat is not actually returning a value but calling function that you pass a callback function which is returning a value. You need to wait until the geocode operation is complete to retrieve the value of numfinal.
Your function codeAddressLat is not returning any value at all hence you are getting undefined as output.
The geocode request in the codeAddressLat method call and will not return the value to the caller. Your return is of a different scope.
Does this work?
function codeAddressLat(inputcity) {
geocoder = new google.maps.Geocoder();
var latlng = new google.maps.LatLng(-34.397, 150.644);
return geocoder.geocode( { 'address': inputcity}, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
var llsplit = new Array();
bkresult = String(results[0].geometry.location);
bkresult = bkresult.replace(/[\(\)]/g, "");
llsplit = bkresult.split(',');
numfinal = parseFloat(llsplit[0]);
return numfinal;
} else {
log('<b><font color="#C40031">Geocode was not successful:</b> ' + status);
}
});
}
That geocoder.geocode function looks like it is asynchronous. Your codeAddressLat and codeAddressLng functions returns void before the geocoder.geocode function has got data back from the server.
A way to get around it is to nest your calls to geocoder.geocode and use variable scoping so that when all the AJAX calls have returned you can call your addToGlobe function passing the two parameters you want.
Something like this:
codeAddressLatAndLong(pmcity);
function addToGlobe(pmlatlat, pmlatlong) {
log(pmlat); // PROBLEM! Prints 'undefined'
log(pmlong); // PROBLEM! Prints 'undefined'
// Rest of function removed to keep it simple
}
function codeAddressLatAndLong(inputcity) {
// stuff
geocoder.geocode( { 'address': inputcity}, function(results, status) {
// stuff goes here
pmlat = parseFloat(llsplit[0]);
geocoder.geocode({...}, function(results, status) {
// more stuff
pmlatlong = something;
addToGlobe(pmlatlat, pmlatlong);
});
});
}
Welcome to the world of AJAX.

AJAX callback in Obj constructor

I have a class object called Location that works with Google in order to geocode a given address.
The geocode request is made trough an AJAX call and handled via a callback that will initiate the class members once the response arrives.
Here is the code:
function Location(address) {
this.geo = new GClientGeocoder();
this.address = address;
this.coord = [];
var geoCallback = function(result) {
this.coord[0] = result.Placemark[0].Point.coordinates[1];
this.coord[1] = result.Placemark[0].Point.coordinates[0];
window.alert("I am in geoCallback() lat: " + this.coord[0] + "; lon: " + this.coord[1]);
}
this.geo.getLocations(this.address, bind(this, geoCallback));
}
Location.prototype.getAddress = function() { return this.address; }
Location.prototype.getLat = function() { return this.coord[0] }
Location.prototype.getLng = function() { return this.coord[1] }
My question is: it's possible to wait the response from Google before exiting the constructor?
I have no control over the AJAX request since it's made trough Google APIs.
I want to be sure that this.coord[] is properly initialized once a Location obj is created.
Thank you!
No, you can't (read: should not) wait. This is why it's called AJAX ("Asynchronous Javascript ...") in the first place. ;)
You could use a callback function yourself (untested code ahead).
function Location(address, readyCallback) {
this.geo = new GClientGeocoder();
this.address = address;
this.coord = [];
this.onready = readyCallback;
this.geo.getLocations(this.address, bind(this, function(result) {
this.coord[0] = result.Placemark[0].Point.coordinates[1];
this.coord[1] = result.Placemark[0].Point.coordinates[0];
if (typeof this.onready == "function") this.onready.apply(this);
}));
}
Location.prototype.getAddress = function() { return this.address; }
Location.prototype.getLat = function() { return this.coord[0] }
Location.prototype.getLng = function() { return this.coord[1] }
// ... later ...
var l = new Location("Googleplex, Mountain View", function() {
alert(this.getLat());
});
Is it possible to wait the response
from Google before exiting the
constructor?
I wouldn't recommend this approach. When you create a JavaScript object, you don't normally expect it to block for hundreds of milliseconds, until Google responds.
In addition, Google will throttle the GClientGeocoder if you attempt to do frequent requests (Source). There is also a cap for the number of requests a client can do in 24 hours. This would be complicated to handle systematically using this approach. You could easily get into a debugging nightmare if you will be having JavaScript objects that fail randomly.

Categories