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.
Related
I have below code in main.html
if(typeof(Worker) !== "undefined") {
if(typeof(w) == "undefined") {
w = new Worker("/location_worker.js");
}
w.onmessage = function(event) {
sequence = event.data;
if(Cookies.get("location_tracked") == "done")
{
w.terminate();
w = undefined;
return;
}
if(navigator.geolocation)
{
navigator.geolocation.getCurrentPosition(function(position)
{
lat = position.coords.latitude;
lon = position.coords.longitude;
$.ajax({
type: "POST",
url: "/api/",
data: {'action':'receive_loc', 'lat': lat,'lon': lon,'enc_data': enc_data,'reached': reached , 'sequence' : sequence },
success: function(jres)
{
res = JSON.parse(jres);
stop = false;
if(res.status == 11)
{
stop = true;
}
if(!stop)
{
//loop continues
}else{
finished = true;
w.terminate();
w = undefined;
return;
}
},
});
},function(code, message) {
$('#error').html("<b>Location Sharing blocked by User..</b>");
});
} else{
alert("Sorry, your browser does not support HTML5 geolocation.");
}
};
} else {
document.getElementById("result").innerHTML = "Sorry, your browser does not support Web Workers...";
}
And the location_worker.js contains below
var i = 0;
var ft = 2000;
function timedCount() {
i = i + 1;
self.postMessage(i);
console.log(i);
setTimeout("timedCount()",ft);
}
timedCount();
so for every 2 seconds the ajax will be submitted which is working fine but in below scenarios its not working as expected
for example if sequence 1 and 2 worked fine and tab/browser got inactive and after sometime if i return back to the this page, all the subsequent requests (which are supposed to be sent when tab is inactive) are getting sent at same time (sequence 3,4,5,6,7,8,9 etc).so all these requests are sending same data with same lat lon values which is not supposed to be happened.
someone please let me know what is wrong with this flow.
Your code gets throttled when the tab doesn't have focus. If you want to ensure you don't make two calls within two seconds of one another, use Date.now to track when you did your last call and don't do another call if it's within 2000 of the current Date.now. E.g., just outside your message handler:
var nextAllowed = Date.now();
then just inside it:
if (Date.now() < nextAllowed) {
return;
}
nextAllowed = Date.now() + 2000;
A few things I happened to notice about that code you might want to consider changing:
There's no point to that web worker at all, just put that timer loop in your main page's code.
typeof isn't a function, there's no need to wrap its operand in ().
Don't pass strings into setTimeout, pass in a function reference instead. So: setTimeout(timedCount, ft);
Unless you have var declarations you haven't shown, your code is falling prey to what I call The Horror of Implicit Globals. Declare your variables (like w, sequence, etc.).
If your /api/ endpoint correctly identified the Content-Type of the response, you wouldn't have to use JSON.parse in the ajax callback. In general, try to ensure responses are sent with the correct Content-Type heder.
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);
});
}
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 8 years ago.
I am trying to learn Node.js
I am having trouble creating my own call back on a function. It seems like such a simple thing but I don't quite understand how to do it.
The function is passed an address (example: "1234 will ln, co") which uses google's geolocate json api to return the full address, latitude and longitude in an array.
Here is my code:
//require secure http module
var https = require("https");
//My google API key
var googleApiKey = "my_private_api_key";
//error function
function printError(error) {
console.error(error.message);
}
function locate(address) {
//accept an address as an argument to geolocate
//replace spaces in the address string with + charectors to make string browser compatiable
address = address.split(' ').join('+');
//var geolocate is the url to get our json object from google's geolocate api
var geolocate = "https://maps.googleapis.com/maps/api/geocode/json?key=";
geolocate += googleApiKey + "&address=" + address;
var reqeust = https.get(geolocate, function (response){
//create empty variable to store response stream
var responsestream = "";
response.on('data', function (chunk){
responsestream += chunk;
}); //end response on data
response.on('end', function (){
if (response.statusCode === 200){
try {
var location = JSON.parse(responsestream);
var fullLocation = {
"address" : location.results[0].formatted_address,
"cord" : location.results[0].geometry.location.lat + "," + location.results[0].geometry.location.lng
};
return fullLocation;
} catch(error) {
printError(error);
}
} else {
printError({ message: "There was an error with Google's Geolocate. Please contact system administrator"});
}
}); //end response on end
}); //end https get request
} //end locate function
So when I try to execute my function
var testing = locate("7678 old spec rd");
console.dir(testing);
The console logs undefined because its not waiting for the return from locate (or at least I am guessing this is the problem).
How do i create a call back so when the locate function returns my array, it runs the console.dir on the array it returned.
Thanks! I hope my question makes sense, im self taught so my tech jargon is horrible.
You need to pass in the callback function to your method - so the callback might look something like this
function logResult(fullLocation){
console.log(fullLocation)
}
You would pass this in to your locate method along with the input:
// note: no parentheses, you're passing a reference to the method itself,
// not executing the method
locate("1234 will ln, co",logResult)
You can also do this inline - much like the response object you're already dealing with:
locate("1234 will ln, co",function(fullLocation){
// do something useful here
})
Now for the bit inside your method, instead of trying to return the result you just call the callback with the result:
function locate(address, callback) {
......
response.on('end', function (){
if (response.statusCode === 200){
try {
var location = JSON.parse(responsestream);
var fullLocation = {
"address" : location.results[0].formatted_address,
"cord" : location.results[0].geometry.location.lat + "," + location.results[0].geometry.location.lng
};
callback(fullLocation); // <-- here!!!
} catch(error) {
printError(error);
}
} else {
printError({ message: "There was an error with Google's Geolocate. Please contact system administrator"});
}
}); //end response on end
.....
}
Javascript : return XMLHttpRequest out of scope
I need to return the data from my AJAX call
series: [{
data: ( )
in order to update one of the keys data in dictionary series but my function retrieve does not seem to return the data that I am getting.
var myPopulation = {
series: [{
data: (
function() {
function retrieve() {
var httpRequest = new XMLHttpRequest();
httpRequest.onreadystatechange = function() {
if (httpRequest.readyState === 4) {
if (httpRequest.status === 200) {
var obj = JSON.parse(httpRequest.responseText)
console.log(obj.V1, obj.V2, obj.V3, obj.V4);
var data = [],
time = (new Date()).getTime(),
i;
for (i = -60; i <= 0; i++) {
console.log(obj.V2)
data.push({
x: time + i * 60 * 1000,
y: obj.V2
});
}
myPopulation.series[1].data = data
// ???
console.log(data)
}
}
};
httpRequest.open('GET', "/myCall");
httpRequest.send();
}
retrieve();
}()
)
}],
What should I do to return the value out of the function and update data?
Well, since you are using jQuery tag, I think my answer could be valid and I prefer doing this way for what you need and I understood (it is well explained so please read code comments and check browser console, this can be found at the end of the answer).
Remember that you won't be able to return a XMLHttpRequest because ajax calls are async but you can force an ajax call to be sync in order to get your data on a return statement from any function or do other things as you expected. However, forcing is not a good approach to do because UI will freeze for the user until getting the response back from server and you really don't know how much time that will take (specially if you are expecting a big amount of data to be returned - I know that's not entirely a metric but other factors may apply).
Hope this helps and please take your time and read the following post and user comments: Reasons not to use native XMLHttpRequest - why is $.ajax mandatory?
Live Demo: http://jsfiddle.net/4mbjjfx8/
HTML:
<div id="wrapper">
<div id="loader"></div>
<div id="content"></div>
</div>
jQuery
$(function() {
var series = [], // Your series array
loader = $('#loader'), // UI loader sample
request = {}; // Request params
/**
* Set request method, url and data if needed
* In this case I am sending an object with a text property
* that will be returned from jsfiddle "echo" service
*/
request.method = 'GET';
request.url = '/echo/jsonp/';
request.data = {
text: 'Second message returned from "echo" service'
};
// Send ajax call
retrieveData(request, series, handleData);
// Set loading message to UI
loader.html('Loading...');
// Just do some logging to know how process goes
console.log('Populating series for the first time');
/**
* Populate series for the first time, at this point process
* will go on and after the response from server was finally
* done, process will go to the callback (since ajax calls
* are async).
*/
populate(series);
// Just do some logging to know how process goes
console.log('End populating series for the first time');
});
function populate(series) {
var dummy = {
text: 'First message populated over process'
};
// Set dummy object to series array
series.push(dummy);
};
/**
* Used to make ajax call and return data from server
*/
function retrieveData(cfg, series, callback) {
$.ajax({
type: cfg.method,
url: cfg.url,
data: cfg.data
}).done(function(data, status, xhr) {
// Pass args to callback function if defined
if (callback) callback(series, data);
}).fail(function(xhr, status) {
/**
* Pass args to callback function if defined
* At this point, request wasn't success so
* force data arg at callback to be 'null'
*/
if (callback) callback(series, null);
});
};
/**
* Used to handle data returned from server
* Note: Your series array can be modified here since you
* passed it into the callback
*/
function handleData(series, data) {
var loader = $('#loader');
// Just do some logging to know how process goes
console.log('Populating series from server');
// Check if data is defined and not an empty object
if(data && !($.isEmptyObject(data))) {
// Add it to series array
series.push(data);
}
// Set UI loader empty
loader.html('');
// Retrieve series
showData(series);
};
function showData(series) {
var contentDiv = $('#content');
// Loop process and append to UI
for(var i = 0; i < series.length; ++i) {
contentDiv.append(series[i].text + '<br>');
}
};
You should put retrieve function outside. You can invoke retrieve function. And, It will call ajax. When ajax is success, it will update data of population. Like this.
var myPopulation = {
series: [{
data: undefined
}]
};
function retrieve() {
var httpRequest = new XMLHttpRequest();
httpRequest.onreadystatechange = function() {
if (httpRequest.readyState === 4) {
if (httpRequest.status === 200) {
var obj = JSON.parse(httpRequest.responseText)
console.log(obj.V1, obj.V2, obj.V3, obj.V4);
var data = [],
time = (new Date()).getTime(),
i;
for (i = -60; i <= 0; i++) {
console.log(obj.V2)
data.push({
x: time + i * 60 * 1000,
y: obj.V2
});
}
myPopulation.series[0].data = data
// ???
console.log(data)
}
}
};
httpRequest.open('GET', "/myCall");
httpRequest.send();
}
retrieve();
Assuming you're simply trying to set the value of myPopulation.series[0].data when the array is first defined...
myPopulation.series[1].data = data
...should be...
myPopulation.series[0].data = data;
Also, some parts of you code are missing closing semicolons, closing brackets and/or curly brackets. Please make sure you end all statements with a semicolon and you have an equal number of opening and closing (curly) brackets.
I've tested your code with the above changes. The HTTP request I made returned a simple "Test successful" string, so I've replaced the code which handles the structure of the response text to simply var data = httpRequest.responeText;. This worked fine. Of course, this assumes the code which handles the structure of the returned httpRequest.responeText in your case is correct, as I have no way of knowing what the responseText in your case looks like. If you receive any errors regarding this part of your code, we'll need to see what the responseText looks like before we can help you.
I'm not judging whether you are doing the right thing. Im merely presenting you a working version of your code.
Errors in your code:
You mean to set result of the "function" to data but your function is not returning anything in the first place.
XMLHttpRequest is async so even if you return you will not have the data set, simply because the outer function exited after making the http request setting a callback to trigger when it is completed.
Note: The fix is by making XMLHttpRequest synchronous.
https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Synchronous_and_Asynchronous_Requests
Here is the corrected version of your code
var myPopulation = {
series: [{
data: (
function() {
function retrieve() {
var httpRequest = new XMLHttpRequest();
var result = []; //[1] just renamed data to result to avoid confusion
httpRequest.onreadystatechange = function() {
if (httpRequest.readyState === 4) {
if (httpRequest.status === 200) {
var obj = JSON.parse(httpRequest.responseText)
console.log(obj.V1, obj.V2, obj.V3, obj.V4);
var time = (new Date()).getTime(),
i;
for (i = -60; i <= 0; i++) {
console.log(obj.V2)
result.push({
x: time + i * 60 * 1000,
y: obj.V2
});
}
//myPopulation.series[1].data = data //[2] commented this as it is not necessary
// ???
console.log(result)
}
}
};
httpRequest.open('GET', "/myCall", false); //[3] Added 3rd argument 'false' to make the call synchronous
httpRequest.send();
return result //[4] to convey the result outside
}
return retrieve(); //[5] added return to set it to the data
}()
)
}],
The above code is not tested however. Here is a tested solution http://jsfiddle.net/98f9amo8/1/
The jsfiddle content is different for obvious reasons.
Working with async code means you have to change the way you code because the code is not executed top-down any more.
So, in your case, you would do something like:
var myPopulation = {series: []};
$.get(..., my_function_that_will_format_the_data_after_they_have_been_received);
...
my_function_that_will_format_the_data_after_they_have_been_received() {
// Do stuff here
var formattedData = ...
myPopulation.series.push(formattedData);
// ONLY NOW, myPopulation is ... populated with data.
// So, whatever you use this for, need to be called here
doMagicWith(myPopulation);
}
...
/// Here, myPopulation is empty. doMagicWith(myPopulation) will fail here.
I do not know the context of how you are doing this, seeing no jQuery tells me you wish to avoid it.
So no matter what happens the call is going to take time, and you need to wait for it for whatever you may need to do with it. Loaders can help tell a user that its processing but there are other ways to do that as well. The common factor is no matter what the data is not going to be there when you need it unless you do some sort of callback.
So here is an idea, create your on onload event more or less. There are many things to keep an eye on so jQuery's is probably the most complete, but going to keep it simple here.
window.isLoaded = false;
window.ajaxLoaded = false;
window.onload = function(){
if(window.ajaxLoaded){
onReadyFunction();
}else{
window.isLoaded = true;
}
}
//skipping most of your code, the key part is the retrieve function.
//So its the only part I am going to include in this part.
function retrieve() {
var httpRequest = new XMLHttpRequest();
httpRequest.onreadystatechange = function() {
if (httpRequest.readyState === 4) {
if (httpRequest.status === 200) {
var obj = JSON.parse(httpRequest.responseText)
console.log(obj.V1, obj.V2, obj.V3, obj.V4);
var data = [],
time = (new Date()).getTime(),
i;
for (i = -60; i <= 0; i++) {
console.log(obj.V2)
data.push({
x: time + i * 60 * 1000,
y: obj.V2
});
}
myPopulation.series[1].data = data
// ???
console.log(data)
}
}
//here is my only addition
if(window.isLoaded){
onReadyFunction();
}else{
window.ajaxLoaded = true;
}
};
httpRequest.open('GET', "/myCall");
httpRequest.send();
}
So all I am doing is adding another part to the typical DOM load. Waiting for the data you need to be available before it initialized the rest of the JS. Doing this you can keep the least downtime for your app (although it depends on where you are trying to get this data though). All you need is to define the onReadyFunction like so.
function onReadyFunction(){
//all the rest of your JS here
}
This can be expanded and organized very easy, just a simple example to get started.
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);
});