Ajax callback and variable scope when loading JSON file into array - javascript

I am loading a geoJSON file using ajax so that I can push the coordinates of the geoJSON MultiPoint feature that is in the file into a (global) array for use in another function. I am having a problem with my callback function, I think. In the code below, I intend to load the data file and push the data pertaining to certain whales into the multipointCoords array. This works fine, but multipointCoords array is not available globally while whales is available. This is confusing me.
This code is also using OpenLayers.
var whales = {};
var multipointCoords = [];
$.getJSON('data/BW2205005.geojson', function(data) {
data.features.forEach(function(feature) {
if (!whales.hasOwnProperty(feature.properties.name)) {
whales[feature.properties.name] = {
"type": "FeatureCollection",
"features": []
};
}
whales[feature.properties.name].features.push(feature);
whales["Free Willy"].features.forEach(function(feature) {
multipointCoords.push(feature.geometry.coordinates);
});
console.log(multipointCoords[0][0]); // gives the right coordinate
});
});
console.log(whales); // is defined
console.log(multipointCoords[0][0]); // is undefined
I have studied the following and still can't fix it:
How to load Open layers 3 geojson vector layer with bbox?
How do I return the response from an asynchronous call?
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
http://api.jquery.com/jQuery.getJSON/
Thanks for any help.

While making ajax calls, we are uncertain about when the success callback will fire, hence we user callbacks in such situations. And pass function as an argument to the called function. In callback function we can pass the data retrieved in ajax response. I could see you are using global-variable in your snippet but you are using them synchronously, in that case value of global-variable is not updated(Values used before ajax success callback)
Try this:
function geoJSON(callback) {
var whales = {};
var multipointCoords = [];
$.getJSON('data/BW2205005.geojson', function(data) {
data.features.forEach(function(feature) {
if (!whales.hasOwnProperty(feature.properties.name)) {
whales[feature.properties.name] = {
"type": "FeatureCollection",
"features": []
};
}
whales[feature.properties.name].features.push(feature);
whales["Free Willy"].features.forEach(function(feature) {
multipointCoords.push(feature.geometry.coordinates);
});
if (typeof callback === 'function') {
callback(whales, multipointCoords);
}
});
});
}
geoJSON(function(whales, multipointCoords) {
console.log(whales);
console.log(multipointCoords);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

You array is defined as an empty array, but its first member - not yet.
You are trying to access your array members before your callback fetches the data and process it. To fix it you may use promises.
var whales = {};
var multipointCoords = [];
$.getJSON('data/BW2205005.geojson')
.then( function(data) {
data.features.forEach(function(feature) {
if (!whales.hasOwnProperty(feature.properties.name)) {
whales[feature.properties.name] = {
"type": "FeatureCollection",
"features": []
};
}
whales[feature.properties.name].features.push(feature);
whales["Free Willy"].features.forEach(function(feature) {
multipointCoords.push(feature.geometry.coordinates);
console.log(multipointCoords[0][0]); // gives the right coordinate
});
});
})
.then( function() {
console.log(whales); // is defined
console.log(multipointCoords[0][0]); // is undefined
});

multipointCoords[0][0]) will be always undefined because you're trying to access a value which is not even initialized by the time your console.log() executes.
Even though it looks like the statements in your code are executed in top-bottom fashion, $.getJSON() is an Asynchronous operation, so JS interpreter will fire your AJAX call (via $.getJSON) and continue to execute the remaining statements in your script. The callback that you're passing to $.getJSON() is invoked only when your AJAX call is success and some response is available.
I've attached a screenshot trying to depict the way in which your statements are executed. As shown below, by the time step 5 is executed, multipointCoords is not populated with your expected data as $.getJSON's callback is not invoked yet. Once the AJAX call is done, callback is invoked (Step 6) and multipointCoords is populated with your expected values. Hence the console.log() inside your callback prints some valid value.
If you intend to use multipointCoords in some other function, you can either invoke that other function in the success callback of $.getJSON or use Promises.
$.getJSON('data/BW2205005.geojson', function(data) {
data.features.forEach(function(feature) {
/* do your logic here
populate multipointCoords*/
multipointCoords.push(feature.geometry.coordinates);
});
}).done(function(){ // done is called only for success scenarios
anotherFunction(multipointCoords);
}).fail(function{
// AJAX call failed, multipointCoords is not populated, handle it
});
Hope this helps :)

Related

Why is my callback in JavaScript not working for asynchronous results?

I have been trying to set up callbacks to get the results from an asynchronous operation, but I have been unsuccessful so far. Have a look at the structure of the code below.
var markers = []; //global array
//The callback parameter is a reference to the function which is passed as an argument from the doAsyncOperation call
function doAsyncOperation(callback) {
var xmlArray = []; //the array that will hold the data that I'm trying to access later in the code
downloadUrl("theXmlFile.xml", function (data) { //this is the async code that stores the results in an array called xmlArray
var xmlItems = data.documentElement.getElementsByTagName("row");
xmlArray.push(theData); //this array is populated with data within this async code block
//the logic works here; this part is not the issue
});
setTimeout(function () {
callback(xmlArray); //passing xmlArray as the result
}, Math.random() * 2000);
}
//the code below is the function call that should be getting the results from the doAsyncOperation function
doAsyncOperation(function (xmlData) {
alert(xmlData); //I am not able to see the data in xmlArray here
});
//the function below is called on Window.onload
function initialize() {
//In this function, I need to use the results in the xmlArray above.
//I have tried saving the results into a global array, but it doesn't work because this function is called
//before the async operation completes.
}
To sum it up, I am having trouble accessing the results of an asynchronous operation. As you can see, I have tried to set up callbacks to access the data, but I have to be doing something wrong. I have looked at similar questions here, but none of them seem to address the issue I'm having specifically. Any help is appreciated.
You have two variables called xmlArray.
One is in the scope of doAsyncOperation.
The other is in the scope of the anonymous function you pass as an argument to downloadUrl.
Both of them start out by having empty arrays assigned to them.
The second one has some items pushed into it.
The first one is the one you pass to callback
Remove the line
var xmlArray = []; //this array is populated with data within this async code block
If you want the code in the anonymous function to modify the first array instead.
NB: Since you try to deal with the data Math.random() * 2000 after sending the request, it is possible that you will not have received a response (triggering the anonymous function) before you try to pass it to callback.
Shouldn't this be like shown below - the callback is invoked after downloadUrl finishes
function doAsyncOperation(callback) {
var xmlArray = [];
downloadUrl("theXmlFile.xml", function (data) {
var xmlItems = data.documentElement.getElementsByTagName("row");
xmlArray.push(theData);
callback(xmlArray);
});
}
doAsyncOperation(function (xmlData) {
alert(xmlData);
});

JS asynchronous function in a loop

I've got some experience in PHP, but I'm starting out with javascript and jquery. I'm working on my first project. I thought that scripting is scripting, and there will be little difference between this and PHP. Well was I wrong. For the first time I saw that something which is first in the code executes last!
Please have a look at this function which is meant to get svg and store them in json object to use as inline svg later
var svgIcons = { "arrow_left": "", "arrow_right":"", } //json object with empty values
this.getIcons = function() {
for (var icon_name in svgIcons) {
if (svgIcons.hasOwnProperty(icon_name)) {
var url=PHP.plugin_url+'/includes/icons/'+icon_name+'.svg';
jQuery.get(url, function(data) {
svgIcons[icon_name]=data;
console.log('iterating');
console.log(svgIcons[icon_name]); //outputs svg
});
}
}
console.log('this should be after iteration');
console.log(svgIcons["arrow_left"]); //empty
}
this.getIcons(); //called at object initialization
But the output is:
this should be after iteration
iterating
#document (and svg inside it)
iterating
#document (and svg inside it)
What is the cause of this change of order? Is it the get() function? How do I avoid situations like this?
jQuery.get is asynchronous. You are iterating inside the callback for an AJAX call, so that gets executed whenever the AJAX call is completed.
AJAX callbacks, setTimeout and setInterval are some asynchronous Javascript functions. Some threads you might find useful:
How does Asynchronous Javascript Execution happen?
Are all javascript callbacks asynchronous? If not, how do I know which are?
Edit: Yes, the function call ends before any of the callback stuff happens. Basically the execution of your JS will be linear, placing functions on a call stack whenever they are called. On the call-stack they are executed one-by-one, line-by-line. However, when one of those lines calls an asynchronous function (like a setTimeout or AJAX), the current execution places the async function on the call-stack and immediately returns to complete itself. So something like:
function myFunc(){
console.log('a');
setTimeout(function(){
console.log('b');
},0)
console.log('c');
}
myFunc();
would always log:
a
c
b
...even though the setTimeout is 0.
So, in your case what must be happening is that you are assigning the AJAX-received data to svgIcons[icon_name] inside the async callback (obviously), while the rest of your code which uses the object svgIcons is in the sequential/normal execution. You either have to move the code that uses the object inside the async callback, or use promises (basically promises are functions that are executed after an async call is completed).
2nd Edit: So, the reason you are not able to set svgIcons[icon_name] inside the callback is related to the things I was mentioning in my comment. When synchronous functions are called, they are placed on top of the current stack and executed right away, before returning to the calling function. So if you called a sync function inside a loop:
function outer(){
function inner(){
console.log(i);
}
for(var i=0;i<3;i++)
inner();
}
outer();
the synchronous inner function would be executed right away inside each loop, and would have access to the current value of i, so it would output 0, 1, 2 (as expected).
If however, inner was asynchronous, e.g
function outer(){
for (var i=0;i<3;i++)
setTimeout(function(){console.log(i)},0);
}
Then you would get 3, 3, 3 as the output!
This is because the loop has already finished, including the final i++.
So now I think you can see the problem with your code. Upto calling jQuery.get you have access to the current value of icon_name, but once we are inside that asynchronous callback, the current value disappears and is replaced by the last value for it, because the loop already completed before any of the callbacks were executed.
Try something like this:
var svgIcons = {}
var props = ["arrow_left","arrow_right"];
this.getIcons = function() {
props.forEach(function(prop){
var url=PHP.plugin_url+'/includes/icons/'+prop+'.svg';
jQuery.get(url, function(data) {
svgIcons[prop]=data;
var fullyLoaded = false;
for(var i=0;i<props.length;i++) {
if(!svgIcons.hasOwnProperty(props[i])){
fullyLoaded = false;
break;
}
else fullyLoaded = true;
} // end for loop
if(fullyLoaded)
callMyFunctionWhereIUseSvgIconsData();
}); //end jQuery.get()
});//end forEach
}
this.getIcons()
This uses the forEach method, which is native to arrays (MDN reference). Inside the function passed to forEach, the first argument is always the current element of the array (which I named as prop). So there is no messy loop or i, and every executing function has access to its own prop property.
Then, inside the AJAX callback, I assign the current prop to the data received, and then loop through all the properties to check if the svgIcons object has received the properties. So fullyLoaded will only evaluate to true once all the callbacks have been executed and the global svgIcons has received all the properties and data. Hence, you can now call the function that uses the object.
Hope this helps, feel free to ask further or let me know if the console throws errors.
Any ajax calls are async therefore it can be run while the ajax call is taking place. If you want to call something after all calls are done then try this:
var svgIcons = { "arrow_left": "", "arrow_right":"", } //json object with empty values
var executing = 0;
this.getIcons = function() {
for (var icon_name in svgIcons) {
//store that this call has started
exectuing = executing + 1;
if (svgIcons.hasOwnProperty(icon_name)) {
var url=PHP.plugin_url+'/includes/icons/'+icon_name+'.svg';
console.log('this will run as you were expecting');
//this ajax call is then started and moves to next iteration
jQuery.get(url, function(data) {
//This is run after the ajax call has returned a response, not in the order of the code
svgIcons[icon_name]=data;
console.log('iterating');
console.log(svgIcons[icon_name]); //outputs svg
//if you want to call a function after evey call is comeplete then ignore the 'executing' part and just call the function here.
//decrement value as this call has finished
executing = executing - 1;
//if all have finished then call the function we want
if(executing === 0){
executeAfter();
}
});
}
}
console.log('this should be after iteration');
console.log(svgIcons["arrow_left"]); //empty
}
this.executeAfter(){
//This will be exectued after all of you ajax calls are complete.
}
this.getIcons(); //called at object initialization

variable scope in d3 javascript

I want to get data in global variable by using the following code:
var data;
d3.json ( "file.json" , function(json) {
data = json;
console.log(data); //defined
});
console.log(data); //undefined
But the problem is that i just have data variable defined in d3.json function but out it is undefined.
how can I solve this issue?
Thanks
Because d3 requests (like d3.json) are asynchronous, it's best practice to wrap all of the code dependent on your external request within the request callback, ensuring that this code has access to the data before executing. From the D3 docs: "When loading data asynchronously, code that depends on the loaded data should generally exist within the callback function."
So one option is to put all of your code within the callback function. If you'd like to separate the code into parts, you can also pass the response from your request to a separate function, something like this:
function myFunc(data) {
console.log(data);
}
d3.json('file.json', function (data) {
var json = data;
myFunc(json);
});

Synchronous function calls involving post json calls where one function should succeed upon the success of another function

I have two functions one of which includes multiple json call which are post by nature.
I want these to be synchronous. That is, one should run only upon the completion of the previous post (and if all posts are done and successful I want the second function to fire).
The code structure is somewhat like this:
$.getSomeData = function() {
$.postJSON("iwantdata.htm",{data:data},function(data)){
});
$.postJSON("iwantmoredata.htm",{data:data},function(data)){
});
});
$.useSomeData = function() {
});
The useSomeData must work upon subsequent json calls.
Can anyone please help me? Thanks in advance.
So basically you want something like this:
function chainPost(url1, url2, initialInput, func) {
$.post(url1, {data: initialInput})
.done(function (initialOutput) {
$.post(url2, {data: initialOutput})
.done(function (secondOutput) {
func(initialOutput, secondOutput);
});
});
}
chainPost("iwantdata.htm", "iwantmoredata.htm", 0, function (first, second) {
alert(first);
alert(second);
});
You can just nest them, starting the 2nd one in the completion function of the first and so on:
$.getSomeData = function() {
$.postJSON("iwantdata.htm",{data:data},function(data) {
$.postJSON("iwantmoredata.htm",{data:data},function(data)){
// use the data here
});
});
};
When dealing with asychronous functions, you cannot write code such as:
$.getSomeData();
$.useSomeData();
By definition, the first is asynchronous so it will not have completed yet with the second function is called and javascript does not have the ability to stop JS execution until an asynchronous operation is done.
You could pass your use function to the get function and then it would get called when the data was available as an addition to the above example like this:
$.getSomeData = function(fn) {
$.postJSON("iwantdata.htm",{data:data},function(data) {
$.postJSON("iwantmoredata.htm",{data:data},function(data)){
fn(data);
});
});
};
Then, you'd have a getSomeData(useFn) function that would take an argument of the function to call when all the data was ready.
Deferred objects [docs] are perfect for this. Unfortunately, your code example contains syntax errors and it is not clear how the calls are nested. So, I'm not sure if you want to run both Ajax calls after one another or parallel, but either way is possible.
Here are two examples. Have a look at the documentation for more information and play around with it.
Note: .postJSON is not a built in jQuery method, I assume here that you are returning the return value from the $.ajax (or $.post) function.
Parallel Ajax calls:
$.getSomeData = function() {
var a = $.postJSON("iwantdata.htm", {data:data});
var b = $.postJSON("iwantmoredata.htm", {data:data});
// return a new promise object which gets resolved when both calls are
// successful
return $.when(a, b);
};
// when both calls are successful, call `$.useSomeData`
// it will have access to the responses of both Ajax calls
$.getSomeData.done($.useSomeData);
See: $.when
Chained Ajax calls:
... where the response of the first call is the input for the second one. This is only an example, of course you can pass any data you want.
$.getSomeData = function() {
return $.postJSON("iwantdata.htm", {data:data}).pipe(function(response) {
// execute the second Ajax call upon successful completion
// of the first one
return $.postJSON("iwantmoredata.htm", {data:response});
});
};
// if both Ajax calls are successful, call `$.useSomeData`
// it will have access to the response of the second Ajax call
$.getSomeData.done($.useSomeData);
See: deferred.pipe()
If you have a more complex logic, you can also create, resolve or reject your own deferred objects. Have a look at the examples in the documentation.

How do "recursive AJAX callbacks" in JavaScript work?

I am using the Github API to retrieve data about one of my repos and I am running into trouble with callback functions and recursive functions overlapping (as in recursive functions with callbacks attached to them)
Here is the script on jsfiddle as well as below:
(function () {
'use strict';
function makeAJAXCall(hash, cb) {
$.ajaxSetup({
accept: 'application/vnd.github.raw',
dataType: 'jsonp'
});
$.ajax({
url: hash,
success: function (json) {
//console.info(json);
// Time for callback to be executed
if (cb) {
cb(json);
}
},
error: function (error) {
console.error(error);
// an error happened, check it out.
throw error;
}
});
}
function parseBlob(hash) {
return makeAJAXCall(hash, function (returnedJSON) { // no loop as only one entry
console.log(returnedJSON.data);
return returnedJSON.data.content;
});
}
function walkTree(hash) {
var tree = 'https://api.github.com/repos/myusername/SVG-Shapes/git/trees/' + hash;
return makeAJAXCall(tree, function (objectedJSON) {
var objectList = [], i, entry;
for (i = 0; i < objectedJSON.data.tree.length; i += 1) {
entry = objectedJSON.data.tree[i];
//console.debug(entry);
if (entry.type === 'blob') {
if (entry.path.slice(-4) === '.svg') { // we only want the svg images not the ignore file and README etc
//console.info(entry.path)
objectList.push(parseBlob(entry.url));
}
} else if (entry.type === 'tree') {
objectList.push(walkTree(entry.sha));
}
}
if (cb) {
console.log(objectList);
cb(objectList);
}
return objectList;
});
}
$(document).ready(function () {
var returnedObjects = walkTree('master', function (objects) { // master to start at the top and work our way down
console.info(objects);
});
});
}());
The JSON returned is either blog (file) or tree (directory). If it is a tree the walkTree function is called again. I do not understand how the callback will behave here as well as how to get the data it (should) return(s) out of the function and into the final block at the very bottom.
Can someone clarify how I should be doing this?
Ajax calls are usually asynchronous. That means that when you make the ajax call, it just initiates the ajax call and it finishes some time later. Meanwhile, the rest of your code after the initiation of the ajax call keeps running until completion.
Then, sometime later when the ajax call finishes, the success function is called and, in your case, the callback function gets called by the success function. It is important to understand that the success function is called much later after the makeAJAXCall() function has already finished.
Thus, you cannot return the ajax data from the makeAJAXCall() function because it isn't known yet when that function returns.
In fact, the only two places you can use the results of the ajax call are:
In the success handler directly
In some function that the success handler calls which in your case it the callback function.
So, it is doing you no good to return returnedJSON.data.content; from the callback function. That is just returning into some internal part of the ajax infrastructure and doing nothing. That return value will just be dropped on the floor and lost.
Instead, you need to put whatever code wants to use returnedJSON.data.content right there in that callback function (or pass it to another function with a function call).
Ajax is asynchronous. That means you can't do normal sequential programming when using ajax. Instead, you have to do event driven programming where the event in this case is the callback that is called upon a success completion of the ajax call. All work that uses those ajax results needs to commence from that success handler or the callback that is called from it.

Categories