script doesn't allow geolocation.getCurrentPosition to finish its callback function - javascript

I need to package the user's current location into an object and send it off to a PHP script. So what I have currently is the following.
position object
function position(lat, lon) {
this.latitude = lat;
this.longitude = lon;
this.setlat = function(lat) {
this.latitude = lat;
};
this.setlon = function(lon) {
this.longitude = lon;
};
this.print = function() {
alert(this.longitude + ", " + this.latitude);
};
}
control flow
var origin = new position(0, 0);
$(document).ready(function() {
getLocation();
origin.print();
});
functions
function getLocation() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(setLocation, showError, {timeout:60000});
} else {
alert("GeoLocation is not supported on this browser or device.");
return null;
}
}
function setLocation(position) {
alert('callback success!');
origin.setlat(position.coords.latitude);
origin.setlon(position.coords.longitude);
}
function showError(error) {
switch(error.code) {
case error.PERMISSION_DENIED:
console.log("User denied the request for Geolocation.");
break;
case error.POSITION_UNAVAILABLE:
console.log("Location information is unavailable.");
break;
case error.TIMEOUT:
console.log("The request to get user location timed out.");
break;
case error.UNKNOWN_ERROR:
console.log("An unknown error occurred.");
break;
}
}
The problem is that before the setLocation callback is being called, the main control flow continues and ignores the fact that I asked it get something for me. I call getLocation and I get an alert of "0, 0" since that's what origin is originally set to. So for some reason my setLocation callback just isn't getting called until everything else in the program is done.
I've looked around in the documentation, but it's rather straightforward and there isn't a whole lot going on so it's hard to pin down why this is happening.
I know a lot of people have struggled with this and there seems to be a lot of sort of 'look at this topic' but all of these topics point to code that is effectively the same as my own, but formatted a little differently.
It's important to note that I really need this position data encapsulated in a global variable since I'll be comparing my location with several dozen other locations on page-load.
attempted solutions
I've tried restricting my navigator.geolocation.getCurrentPosition() to a while loop that exits based on the completion of the callback function, but this always results in an unending loop.
-I've tried playing with the maximumAge and timeout values in the PositionOptions in order to allow the function some time to finish, but again this just results in setLocation finishing after all other scheduled function calls are done.
-I've tried using two seperate $(document).ready(function(){}); in order to segment the getLocation() and origin.print() functions.
-I've tried using watchPosition instead of getCurrentPosition.
Ultimately I really just need some clarification on why the callback function in getCurrentPosition isn't finishing until everything else does.

While it doesn't really seem all that different from performing logic in your callback function for getCurrentPostion there are some technologies in Javascript which allow you to deal with asynchronicity in this way, and thus considerably clean up your code. What I'll demonstrate below is using jQuery's $.Deferred object to grab your current location and package it into a variable which can be used by other functions.
function getLocation() {
var deferred = $.Deferred();
setTimeout(function() {
navigator.geolocation.getCurrentPosition(function(pos) {
origin = new position(pos.coords.latitude, pos.coords.longitude);
deferred.resolve();
});
}, 2000);
// 2.00 seconds
return deferred.promise();
}
It's important to note that we're instantiating the origin and resolvin the deferred object with deferred.resolve() in the same scope.
Now what we'll have to do is call getLocation() but with some added syntax to specify that the functions dealing with objects resolved inside getLocation() must wait until it's finished to access them.
getLocation().done( function() {
origin.print();
//do other stuff with origin
});
This will successfully print your coordinates, and you're free to do anything else with the data gotten by getCurrentPosition, like using Google Maps.

You can replace this:
var origin = new position(0, 0);
$(document).ready(function() {
getLocation();
origin.print();
});
with this
var origin = new position(0, 0);
$(document).ready(function() {
getLocation();
});
function getLocation() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(setLocation, showError, {timeout:60000});
} else {
alert("GeoLocation is not supported on this browser or device.");
return null;
}
}
// this is the callback
function setLocation(position) {
alert('callback success!');
origin.setlat(position.coords.latitude);
origin.setlon(position.coords.longitude);
// this is a new function that makes stuff
processLocation();
}
// do your stuff here
function processLocation(){
origin.print();
// do your stuff here like comparing to other locations or sending data
if (origin.latitude == something) blah...
}
Basically do your logic inside a function and call it inside the setLocation callback

Related

How to run an async function in js with timeouts (for rate limiting)

I'm designing a system where a user will interact with my RESTful API through JS, and will replace the ts html element with a different SVG if the returned answer is true. If the API call returns false, the JS should wait for 5 seconds, before retrying the API call. again, if the API call returns true, the SVG will be changed, and the function will stop calling the API. How can I have the function "sleep" for 5 seconds before running again, and ending the function if the returned API call is true?
Code:
async function getTransaction(){
var lookup = $("meta[name='lookup']").attr("content");
axios.get('http://127.0.0.1:5000/client_api/transaction_status/' + lookup)
.then(function (response) {
var tStat = response.data.transactionStatus;
console
if(tStat){
document.getElementById("ts").innerHTML = 'drawing the new svg and replacing the old one'
console.log('transaction_found')
}
else if(!tStat){
console.log("transaction not found")
}
else{
console.log("error")
}
});
}console.log("hello"); while(true){setTimeout(getTransaction,5000);}
Don't use sleep sice it pauses your programm to pause. Instead use setTimeout like so. This creates an event listener which calls the function again after 5s if tStat is false.
In case of false the function will set an timeout after which the function is called again. After the timeout is completed it clears itself. This step repeats until the api request eventually resolves to true
async function getTransaction() {
var lookup = $("meta[name='lookup']").attr("content");
axios.get('http://127.0.0.1:5000/client_api/transaction_status/' + lookup)
.then(function(response) {
var tStat = response.data.transactionStatus;
console
if (tStat) {
document.getElementById("ts").innerHTML = 'drawing the new svg and replacing the old one'
console.log('transaction_found')
} else {
console.log("transaction not found")
setTimeout(getTransaction, 5000);
}
});
}
console.log("hello");

Safari ITP 2.0 Storage Access API - Trouble Nesting requestStorageAccess in hasStorageAccess - Non Nested Works

I'm currently trying to implement calling the storage access API, but am having trouble nesting the call to requestStorageAccess in hasStorageAccess.
Here is an outline of the code - it's fairly standard:
requestStorageAccessAndServe() {
let thisObject = this;
var promise = document.hasStorageAccess();
promise.then(
function (hasCookieAccess) {
if (!hasCookieAccess) {
document.requestStorageAccess().then(
function successful() {
// reload iframe to run with cookie access
window.location.reload();
},
function fail() {
thisObject.serveContent(); // Code goes into here
});
} else {
thisObject.serveContent();
}
},
function (reason) {
thisObject.serveContent();
}
);
}
When clicking on the button to trigger this method, I always fall into that "fail" function, with no prompt appearing to request storage access.
Surprisingly, this non-nested code works perfectly:
requestStorageAccessAndServe() {
let thisObject = this;
let hasCookieAccess = !!document.cookie;
if (!hasCookieAccess) {
document.requestStorageAccess().then(
function successful() {
window.location.reload();
},
function fail() {
thisObject.serveContent();
});
} else {
thisObject.serveContent();
}
}
This code works - it reloads the iframe on the first request and then serves the data after another request on reload, but checking for cookie access by doing !!document.cookie is extremely hacky (what if there is no cookie data in the first place), and I'm more trying to understand what is going wrong here. Does anyone have any idea?
For a possible solution, is there any way I can force resolve document.hasStorageAccess() so I don't need to nest it?
Edit:
Forcing the promise to resolve did not help either. See code example:
async requestStorageAccessAndServe() {
let thisObject = this;
let hasCookieAccess = await document.hasStorageAccess();
if (!hasCookieAccess) {
document.requestStorageAccess().then(
function successful() {
window.location.reload();
},
function fail() {
thisObject.serveContent();
});
} else {
thisObject.serveContent();
}
}
Still goes into that "fail" function...
The problem here is that requestStorageAccess() requires user intent to be called. By nesting it in the hasStorageAccess() promise, that user intent (click) is obscured, and Safari rejects the request automatically.
To counteract this, I resolve hasStorageAccess on iframe load (since it does not require user intent), store this result in a class variable, and then if it resolved to false, I check requestStorageAccess on click.

JavaScript set value for variable failed

I'm new to javascript. I'm trying to set value to variable with the following code but failed. Someone please help point out what is wrong with my code.
When I try to print out the value of variable "deviceLatitude", it give me "undefined". But if I print the value from inside the function, it give me the correct value. What have I done wrong?
The reason I need to have global variable for this value is because I need to use it in later stage such as compare distance with different location as needed.
var deviceLatitude;
var deviceLongitude;
function suc (p) {
deviceLatitude = p.coords.latitude;
deviceLongitude = p.coords.longitude;
// alert(deviceLatitude);
}
intel.xdk.geolocation.getCurrentPosition(suc);
alert(deviceLatitude);
suc is being called back asynchronously, so when you issue the alert after the call to intel.xdk...., suc hasn't been called yet.
Note the documentation, my emphasis:
Use this command to get the current location. This command
asynchronously acquires the approximate latitude and longitude of the
device. When data is available, the success function is called. If
there is an error getting position data, the error function is called.
Therefore, if you want to do something with deviceLatitude, you'll have to do it inside the callback.
If you're a promises type of guy, you could do:
function getCurrentPosition() {
return new Promise(function(resolve, reject) {
intel.xdk.geolocation.getCurrentPosition(resolve, reject);
});
}
getCurrentPosition.then(
function(p) {
//do something with p.coords.latitude
},
function() {
//something went wrong
}
);
Try to make anonymous function for success and other for error.
Then create another function which will be called by async when data is available.
function overrideLocalStore(lat, log)
{
alert("lat"+lat+" long"+long);
localStorage.setItem("deviceLatitude", lat);
localStorage.setItem("deviceLongitude", log);
}
intel.xdk.geolocation.getCurrentPosition(
function(p)
{
alert("geolocation success");
if (p.coords.latitude != undefined)
overrideLocalStore(p.coords.latitude, p.coords.longitude);
},
function()
{
alert("geolocation failed");
getLocation();
}
);
// Use whatever you need
alert(localStorage.getItem("deviceLatitude"));
alert(localStorage.getItem("deviceLongitude"));
As pointed out by #torazaburo, it's not possible to get data of asynchronously function out into global variable.
But to achieve what is needed, a workaround can handle that. Since this is HTML5 app, one can use localStorage to save the value and access it anytime later from other screen/function.
A simple example code will be as below:
intel.xdk.geolocation.getCurrentPosition(
function(p) {
if (p.coords.latitude != undefined) {
localStorage.deviceLatitude = p.coords.latitude;
localStorage.deviceLongitude = p.coords.longitude;
}
},
function() {
alert("geolocation failed");
}
);

How could I optimize this code with too many nest functions?

I'm writing a Chrome extension with the socket api(though this doc is out of date, the latest version of the api is here), and I found that the code is really hard to organize:
All the methods are under the namespace chrome.experimental.socket, I would just use socket below for simplicity.
socket.create("tcp", {}, function(socketInfo){
var socketId = socketInfo.socketId;
socket.connect(socketId, IP, PORT, function(result){
if(!result) throw "Connect Error";
socket.write(socketId, data, function(writeInfo){
if(writeInfo.bytesWritten < 0) throw "Send Data Error";
socket.read(socketId, function(readInfo){
if(readInfo.resultCode < 0) throw "Read Error";
var data = readInfo.data; // play with the data
// then send the next request
socket.write(socketId, data, function(writeInfo){
socket.read(socketId, function(readInfo){
// ............
});
});
});
})
});
})
because both socket.write and socket.read are asynchronous, I have to nest the callbacks to make sure that the next request is send after the previous request got the correct response.
it's really hard to manage these nested functions, how could I improve it?
UPDATE
I'd like to have a method send which I can use as:
send(socketId, data, function(response){
// play with response
});
// block here until the previous send get the response
send(socketId, data, function(response){
// play with response
});
How about (something like) this?
var MySocket = {
obj: null,
data: null,
start: function() { ... some code initializing obj data, ending with this.create() call },
create: function() { ... some code initializing obj data, ending with this.connect() call },
connect: function() { ... some connection code, ending with this.write() call },
write: function() { ... some writing code that updates this.data, ending with this.read() call },
read: function() { ... you probably get the idea at this point )) ... },
};
This object could be used with MySocket.start() or something. The idea is to encapsulate all data (and nested calls) within the single (yet more-o-less globally usable) object.
Or even more, one can create two objects: one purely for writing purposes, and another for purely reading, each operating with its own data, then wrap them (and their inter-calls, so to speak) into a single SocketManager object.
Consider using an asynchronous continuation passing style, where functions end with a SetInterval call with the function they were passed. Then we construct a function that entwines two functions to call each other using this mechanism. The guts of it would be like this:
var handle;
// pairs two functions
function pair(firstfunc, secondfunc, startarg) {
var callbackToFirst = function(valuetofill) {
handle = setInterval(firstfunc(valuetofill,callbackToSecond));
};
var callbackToSecond = function(valuetofill) {
handle = setInterval(secondfunc(valuetofill,callbackToFirst));
};
callbackToFirst(startarg);
}
What we are doing here is constructing a pair of mutually-calling callbacks which take a single argument, which each contain references to the two inter-calling functions. We then kick off the process by calling the first callback.
Construct the pair for an example pair of read and write functions (assuming you've set the socketId in the enclosing object definition):
// starts read/write pair, sets internal variable 'handle' to
// interval handle for control
function startReadWrite(initialarg, myDataFunc) {
var readcall = function(value, func) {
readSocket(getData(myDataFunc(func)));
};
var writecall = function(value, func) {
writeSocket(checkBytesWritten(func));
};
handle = pair(readcall, writecall, initialarg);
}
The rest of the object is like this:
function myIO() {
var socketInfo, socketId, handle;
function create(func) {
socket.create('tcp',{},function(thisSocketInfo) {
socketInfo = thisSocketInfo;
}
setInterval(func(this),0);
}
function connect(IP, PORT, func) {
socket.connect(p_socketId, IP, PORT, function() {
if(!result) throw "Connect Error";
setInterval(func(result),0);
});
}
function readSocket(func) {
socket.read(p_socketId, function(readInfo){
setInterval(func(readInfo),0);
});
}
function writeSocket(data, func) {
socket.write(p_socketId, data, function(writeInfo){
setInterval(func(writeInfo),0)
});
}
function checkBytesWritten(writeInfo, func) {
if(writeInfo.bytesWritten < 0) throw "Send Data Error";
setInterval(func(writeInfo),0);
}
function getData(readInfo, func) {
if(readInfo.resultCode < 0) throw "Read Error";
var data = readInfo.data;
setInterval(func(data),0);
}
//** pair and startReadWrite go here **//
}
Finally the call to set the whole thing going:
var myIOobj = new myIO();
myIOobj.create(startReadWrite(myDataFunc));
Notes:
This is meant to demonstrate a style, not be ready code! Don't just copy and paste it.
No, I haven't tested this; I do javascript but not Chrome API stuff yet. I'm focussing on the callback mechanisms etc.
Be careful with the different classes of callback; single argument callbacks (like the read and write callbacks) which take a single value (as presumably defined by the API), and 2 argument callbacks (like most of the methods) which take an argument and a function to call at the end.
The getData method takes a callback and passes data to it; this callback (myDataFunc) is the function that actually gets to use the data. It needs to take a callback as a second argument and call it synchronously or asynchronously.
TLDR: Consider using asynchronous calls to avoid the nesting. I've given a vague example of a mechanism to have two functions call each other continuously using this style as seems to be needed.
Although I call it asynchonous, the setInterval calls will execute serially, but the key is that the stack is cleared after the parent call is done, rather than adding endless layers with nesting.

Need help with Extjs function returning undefined results

I want to do the following with my javascript codeblock.
Handle all current and new device requests ie. detect,encrypt,decrypt,etc
Return the result to the calling method
Questions
How can I improve the existing code and get rid of the javascript strict warning:anonymous function does not always return a value.
What is the right way of calling my method?
Any help is greatly appreciated
Thanks!
Herewith the code:
This is how I call the current method
//Contents of SmEditor.js
var response = Ext.decode(Prometheus.DeviceRequestHelper.detect(request_id));
//contents of Sm.js
Ext.ns('myApp')
myApp.DeviceRequestHelper = {
detect:function(request_id){
var task = function(){
Ext.Ajax.request({
url: 'device_requests.php',
params:{
action:'get_device', //in php
'request_id':request_id
},
timeout:30000, //30 seconds
success:function(response){//serverside response
var result = Ext.decode(response.responseText); //convert to js objects
if(result.success == true){//device was detected
cons.log('success,device was detected');
cons.log(result);
Ext.TaskMgr.stop(runTask);
return Ext.encode(result); //javascript strict warning
}else{
if(runTask.taskRunCount >= 10){
//retry limit exceeded
Ext.Msg.show({
title:'Server Failure',
msg:"Detection Failed,Unable to detect device",
icon: Ext.MessageBox.ERROR,
buttons: Ext.Msg.OK
});
Ext.MessageBox.getDialog().getEl().setStyle('z-index','80000');
Ext.TaskMgr.stop(runTask);
}
}
},
failure:function(response){
Ext.TaskMgr.stop(runTask);
Ext.Msg.show({
title:'Server Failure',
msg:"Failed, server communication error",
icon: Ext.MessageBox.ERROR,
buttons: Ext.Msg.OK
});
Ext.MessageBox.getDialog().getEl().setStyle('z-index','80000');
}
})
}
var runTask = {
run: task,
interval:2000,
repeat:10
};
Ext.TaskMgr.start(runTask);
}
}
To prevent this kind of warning, have the function return a value in all cases, or no cases. At the moment you're only returning a value in one if case; the other cases will not return anything. You can even return undefined to make the warning go away. However, what it is telling you is correct: that a function that sometimes has a return value and sometimes doesn't is a bit weird and suggests you're doing something wrong.
What you seem to want to do is have the inner return in the success method return a value from the detect() method. This is absolutely not possible. The inner function can only return a value to the caller of success, which is Prototype itself. By the time this happens, the detect() method has long since returned.
What you have here is asynchronous code. The detect() method can set up an AJAX request, but it must then return immediate to its caller, which will return control to the browser. At some later time, the HTTP request behind the AJAX call will complete, and then the success function will fire. JavaScript cannot call asynchronous code synchronously, or vice versa.
What you have to do is pass a callback function into your method, and then call it back on completion:
Prometheus.DeviceRequestHelper.detect(request_id, function(response) {
// do something with `response`
});
myApp.DeviceRequestHelper= {
detect: function(request_id, callback) {
...
Ext.Ajax.request({
...
success: function(xhr) {
var result= Ext.decode(xhr.responseText);
if (result.success)
callback(result);
...
},
...
});
},
...
};
(I removed the extra Ext.encode->Ext.decode pair, that just seems like a waste of time.)
First, Your detect method will not return a value and will return immediately(even before the ajax call completes) because the ajax call is asynchronous
Second, there's no point returning a value in your success handler. Instead you should provide a callback function to your detect method like so:
Ext.decode(Prometheus.DeviceRequestHelper.detect(request_id, function(response) {
// do something with your response
}));
// detect function takes a callback function as a parameter
myApp.DeviceRequestHelper = {
detect:function(request_id, funCallback){ // pass in a callback function that is
// called when result was a success
var task = function(){
Ext.Ajax.request({
url: 'device_requests.php',
params:{
action:'get_device', //in php
'request_id':request_id
},
timeout:30000, //30 seconds
success:function(response){//serverside response
var result = Ext.decode(response.responseText); //convert to js objects
if(result.success == true){//device was detected
cons.log('success,device was detected');
cons.log(result);
Ext.TaskMgr.stop(runTask);
// return Ext.encode(result); //javascript strict warning
funCallback(Ext.encode(result)); // ===========> callback function called.
}else{
if(runTask.taskRunCount >= 10){
//retry limit exceeded
Ext.Msg.show({
title:'Server Failure',
msg:"Detection Failed,Unable to detect device",
icon: Ext.MessageBox.ERROR,
buttons: Ext.Msg.OK
});
Ext.MessageBox.getDialog().getEl().setStyle('z-index','80000');
Ext.TaskMgr.stop(runTask);
}
}
},
failure:function(response){
// ... failure handing code
}
});
}
var runTask = {
run: task,
interval:2000,
repeat:10
};
Ext.TaskMgr.start(runTask);
}
}

Categories