Accessing Object properties when the object is within an array (javascript) - javascript

I am attempting to create an array of objects and then access object properties within the array, but it comes back undefined. I call the createObjArray() function and immediately after I do a console.log(objArray[1]); and it prints out the object with all it...s properties just fine. However, if I attempt to do console.log(objArray[1].name); firebug prints "undefined". Also, when stepping through my code in firebug I can mouse over objArray[1].name and it displays the correct name. What is happening here, it's driving me nuts.
var objArray = [];
function createObjectArray(numOfObjs) {
for(var i=0; i<numOfObjs; i++) {
packages.push(initObj(i));
}
}
function initObj(i){
var newPackage;
var p = {};
$.getJSON('.../package' + i + '.json', function(data) {
newPackage = new Package(data);
p.name = newPackage.name;
p.id = i;
});
return p;
}

This will work:
var objArray = [];
function createObjectArray(numOfObjs, callback) {
var filledPackage = [];
var nbLeft = numOfObjs;
for(var i=0; i<numOfObjs; i++) {
initObj(i, function(p){
filledPackage.push(p);
nbLeft--;
if (nbLeft === 0){
callback(filledPackage);
}
});
}
}
function initObj(i, callback){
var newPackage;
var p = {};
$.getJSON('.../package' + i + '.json', function(data) {
newPackage = new Package(data);
p.name = newPackage.name;
p.id = i;
callback(p);
});
}
//Get a filled object array:
createObjectArray(5, function(filledArray){
objArray = filledArray;
//Code here will be executed AFTER all the $.getJSON queries have returned.
//objArray is not empty.
});
//Code here will be executed WHILE the getJSON queries are running and
//while objArray is still empty. Due to the way the JS event loop works,
//it is impossible that code placed here will be able to use the content
//of objArray unless you call an async function such as anything AJAX or
//setTimeout, but that's iffy. Code you want to be executed once objArray
//has been filled should be inside of the callback above.
The problem is that $.getJSON is aynchronous, meaning that it doesn't automatically returns a result. Instead, you give it a callback. A callback is a function to execute once it has received a result. In this case, the callback is the anonymous function created when calling $.getJSON. That callback receives the result from the server, adds it to the array and then checks if the array has been filled. Since we're doing async code due to the $.getJSON function, we must return the result asynchronously too. To do so, we demand the initObj function to receive a function to call once it has completed (another callback). We call that callback and pass it the parameter. We then return the filled array through a callback once again.

Your call to $.getJSON is asynchronous. When initObj() returns p it is still an empty object.
However initObj() creates a closure which captures a reference to p so when $.getJSON returns p is populated.
This is why the object seems empty in code you run immediately after populating the array. However by the time you run your console command the asynchronous calls have returned and the objects are populated.
You need to wait for all your async calls to return before continuing work on the array. One way to do this would be to increment a counter when you make a call and decrement it when a call returns, then when the final call returns the counter would drop to zero and you continue processing.
Alternatively you could setup a setTimout loop to keep polling the array the check when all its items are populated.
Both approaches are risky if you think one of the calls might fail, but the approach itself is fundamentally risky as you are making multiple ajax calls so you have to handle multiple possible failures. It would be a lot cleaner to grab all the data in one go so you can handle success / error states once in the success / error handler in jQuery.ajax.

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

Passing variable in parent scope to callback function

This is more of a JavaScript Closure question than a Firebase question. In the following code, the Firebase callback isn't recognizing the variable myArr in the parent scope.
function show_fb() {
var myArr = [];
var firebase = new Firebase('https://scorching-fire-6816.firebaseio.com/');
firebase.on('child_added', function(snapshot) {
var newPost = snapshot.val();
myArr.push(newPost.user);
console.log(myArr); // works
});
console.log(myArr); // doesn't work. myArr in the firebase.on callback is
// not altering myArr
return myArr;
};
The callback is recognizing/modifying myArr perfectly fine. The problem is that when your "doesn't work"-labeled console.log(myArr) executes, the callback hasn't fired yet.
Let's change your code a bit:
var myArr = [];
function show_fb() {
var firebase = new Firebase('https://scorching-fire-6816.firebaseio.com/');
firebase.on('child_added', on_post_added); // steps 1-3
console.log(myArr); // step 4
return myArr; // step 5
};
function on_post_added(snapshot) { // step 6
var newPost = snapshot.val();
myArr.push(newPost.user); // step 7
console.log(myArr); // step 8
}
Now it might be a bit easier to see what's going on.
You register a listener for child_added that will call on_post_added for every post that is added to your Firebase
This will result in a call to the server, which may take a significant amount of time to return
Meanwhile your JavaScript code continues and...
Logs the array, which at this stage is still empty
And then thus returns an empty array
Now at some point the server returns the new value(s) and your callback is invoked
Which means we can add it to the array without problems
And logging it to the console shows the expected values
Handling asynchronous code/callbacks like this takes some getting used to, but is crucial to working with Firebase or any other AJAX-like or event driven technology. Putting the callback's code into a separate function sometimes makes it a bit easier to see what's going on.
In the case of Firebase it may also help to realize that the event is called child_added for a reason. It is called whenever a child is added to the Firebase, not just when you first register your callback. So minutes later when some other client adds a child, your callback will still fire, adding a new child to myArr. At that stage the code in steps 4 and 5 above will long have executed and will not execute again.
The solution is simple: put anything that you want to do after a child is added into your callback:
var myArr = [];
function show_fb() {
var firebase = new Firebase('https://scorching-fire-6816.firebaseio.com/');
firebase.on('child_added', on_post_added);
};
function on_post_added(snapshot) {
var newPost = snapshot.val();
myArr.push(newPost.user);
console.log(myArr);
// do whatever else you need to do for a new post
}
The child_added event is not immediately executed, therefore is not synchronous and you can't rely on it to have executed, before the log call at the end of your function.
The procedure is:
Define myArr
Instantiate Firebase
Assign event handler for child_added
Log the value of myArr
Return myArr - end of function
Now at some point after this, the child_added event is fired, which pushes to your array, but as you can see, your show_fb() function has already finished executing by this point.
If Firebase makes ajax call(it probably does), then callback function(snapshot){..} is called after return statement. So function show_fb always returns [].
for instance:
You execute this statement: var x=show_fb();
show_fb creates empty array
function creates ajax call
function returns myArr (it is empty at this moment)
variable x gets reference to myArr (array is still empty)
callback is called and inserts new value to x (x and myArr have same instance)

forEach, async and callbacks

sorry if this question has been answered before but I couldn't find it.
I have an array of objects, and for each object I want to do an async call (ajax call), and when all async calls are finished, i want to call another function.
eg.
var list = [Object, Object, Object, Object];
var final= [];
$(list).each(function(){
//ajax call
getSomething(data, function(data){
final.push(data);
});
});
After all ajax calls are finished i wanna call function load(final);
Can this be done whith callbacks, and without libraries like when.js etc.
Th.
Call the function when the last item has arrived:
final.push(data);
if (final.length == list.length) load(final);
Since it looks like you have jQuery available, you can use the promises built into jQuery:
var list = [Object, Object, Object, Object];
var promises = [];
$.each(list, function(){
//ajax call
promises.push($.get(data));
});
$.when.apply($, promises).done(function() {
// all ajax calls done
// data from each ajax call is in arguments[0], arguments[1], etc...
load(arguments);
});
One other nice advantage of this mechansim vs. all the others shown so far is that this will keep the results in the order that you requested them, even if they don't come back in that order.
You can also provide a handler to .fail() in addition to .done() (or specify both with a .then(f1, f2)) if you want to catch the case where any ajax call fails.
This is a way to solve the problem with a simple counter
var counter = $(list).length;
$(list).each(function(){
$.get('URL', function(data){
/* do whatever you need for each list item */
if(--counter === 0){
/* Do whatever you wanted to do when all requests completed */
}
});
});
Fundamentally, you keep track of how many calls you've made and how many responses you've gotten, and when you've got all the responses, you call load(final). In your case, quite conveniently you have two arrays and are pushing the results of the calls based on the first array into the second, so you can compare their lengths. (Of course, you'll want to handle the error condition as well.)
Your quoted code is a bit suspect (I think you wanted $.each(list, ..., not $(list).each(...), but I think you probably meant something like this:
var list = [Object, Object, Object, Object];
var final= [];
$.each(list, function(data){
//ajax call
getSomething(data, function(result){ // <= I used `result` rather than `data`; using the same symbol in intermixed code like this is asking for trouble
final.push(result);
if (final.length === list.length) {
// All done
load(final);
}
});
});

trying to store all objects into one object with parse and jquery

I'm trying to store all returned objects into one object for later use. I have the loop set in place but when I try to execute some code with the new object, its empty. In this case here alert the object. I guess the alert is executing before the loop is complete. Any way to fix this?
var followers = new Array;
Parse.initialize("xxxxxx", "xxxxx");
var currentUser = Parse.User.current();
var users = JSON.stringify(currentUser);
var user = eval("(" + users + ")");
var listsfollow = user.Follow;
for (var i = 0; i < listsfollow.length; i++) {
var allUsers = Parse.Object.extend("User");
var query = new Parse.Query(allUsers);
query.get(listsfollow[i], {
success: function (results) {
followers.push(results);
},
error: function (object, error) {
// The object was not retrieved successfully.
// error is a Parse.Error with an error code and description.
}
});
};
alert(followers);
You are correct and the AJAX stuff in jquery is async, and so the .get() will happen after the alert. The quickest and dirtiest way would be to set it to an async to false before issue the request: http://api.jquery.com/jQuery.ajax/
A better way would be to have your code respond to the result of the call whenever it is ready. This will prevent page blocking and make your code faster.
This is an asynchronous query, and the alert is being shown right after the query has initiated, so it's unlikely that it will be executed after the query has returned any results.
Any code that depends on the query's results should be moved into either the success or error callback functions.
I couldn't figure out how to make it wait for the query but I used this to do something after the last loop iteration. Worked great.
if((--remaining)==0)alert(followers);

Categories