I have a problem with my callback functions. My code is supposed to make 16 GET requests to a REST API to pull 16 different JSON files. It then needs to parse each of these JSON's to a dictionary for that week's football table rankings, and ultimately save each entry into a 'dictionary of dictionaries', HistoricalTable, to give the league ranking for the past 16 weeks. However, when I run the associated callback functions, the various LeagueTable variables seem to work fine, but when I try and save these into the Historical Data, the final array appears to have the same LeagueTable entry for each, looking like this.
Here is an image of the console output for my final table. Each entry should be different, whereas each entry seems to be the most recent week.
//This creates the modifier for the URL used in the GET request
var MatchDayList = []
for (i = 0; i < 17; i++) {
MatchDayList[i] = i
}
MatchDayList.shift()
var HistoricalTable = {}
var LeagueTable = {}
// This executes the GET request
for (i = 0; i < 16; i++) {
url = 'http://api.football-data.org/v1/competitions/445/leagueTable/?matchday=' + MatchDayList[i],
$.ajax({
url: 'http://api.football-data.org/v1/competitions/445/leagueTable/?matchday=' + MatchDayList[i],
headers: {
'X-Auth-Token': ''
},
method: 'GET',
dataType: 'json',
success: function(data) {
handleData(data)
},
});
}
//This function should append the retrieved JSON to the LeagueTable variable
function handleData(data) {
for (var j = 0; j < 20; j++) {
LeagueTable[data.standing[j].position] = data.standing[j].teamName
LeagueTable[20] = data.matchday
}
saveData(LeagueTable)
}
//This function should save each LeagueTable matchday data into a bigger array, HistoricalTable
function saveData(LeagueTable) {
HistoricalTable[LeagueTable[20]] = LeagueTable
console.log(HistoricalTable)
}
You are using a single LeagueTable variable throughout the entire code. So every call to handleData populates the same LeagueTable, then tells saveData to store it in the main table. So you end up with 16 references to the same table.
To solve it, it should be enough to move the variable declaration inside handleData function:
function handleData(data) {
var LeagueTable = {};
for (var j = 0; j < 20; j++) {
LeagueTable[data.standing[j].position] = data.standing[j].teamName
LeagueTable[20] = data.matchday
}
saveData(LeagueTable)
}
On a side note, your url variable is not declared anywhere, so it ends up in the global scope, which is generally bad practice. Same with i indices inside your for loops.
Related
I am getting data from my API like this:
function getWeight (username) {
/* This function takes a username as a parameter,
then it calls the API URL using the username
so that it gets the weights of that particular
user.
*/
var weights;
$.ajax({
type: 'GET',
url: '/weight/api/get_weight_data_admin/'+username,
dataType: 'json',
async: false,
success: function (data) {
weights = data
}
});
getWeightPlots(weights);
}
The above code is working as expected when I do console.log(weights) it shows me exactly what I expect:
The problem lies in the code below; the list which I create in the function below, and append values to it inside the while loop, after exiting while loop I see no values in it.
function getWeightPlots (weights) {
/**
* This function takes a list of dictionaries, which contain
* dates when the weight was entered and the weight itself.
* It returns a list of weights that can be used for plotting on
* the graph.
*/
var weightList = [];
// get the last 12 months
var months = getMonths();
var numWeights = weights.length;
var i = 0;
var y = 0;
while (y < months.length) {
var weightMonthNum = weights[i]
weightMonthNum = weightMonthNum['date']
weightMonthNum = weightMonthNum.split('-');
weightMonthNum = parseInt(weightMonthNum[1])
var monthNum = monthToMonthNum(months[y])
if (weightMonthNum < monthNum){
i++;
}
else if (weightMonthNum == monthNum) {
weightList.push(1);
i++;
y++;
}
else {
y++;
weightList.push(0);
}
}
console.log(weightList);
}
I tried debugging and everything seems to be working fine, any ideas?
When getWeightPlots is invoked weights is empty (undefined). Wait for ajax to finish then call it in the success method.
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 5 years ago.
I am doing simple app in javascript. I have "main_script" where I invoke everything. There is global variable "feeds" which is an array, like this:
var feeds = [];
Then after that I use function, that loads JSON file from multipe URLs (also array):
feeds = LoadJsonFeeds(urls); // Load feeds
console.log("main_code feeds.length: " + feeds.length);
That console log I mention later. Ok and now he is my LoadJsonFeeds (in different .js file, just a function):
function LoadJsonFeeds(urls) {
var feeds_tmp = [];
// URLs can be more - for example 50 feeds from url[0] and 20 from url[1]
for(var u = 0; u < url.length; u++) {
$.getJSON(url[u], function(data) {
var allFeeds = data.Result.Items; // allFeeds without check if they are ok
for(var i = 0; i < allFeeds.length; i++) {
// Is feed ok?
if (allFeeds[i].Text != null)
{
// Some more checking, but lets say ok for this
feeds_tmp.push(allFeeds[i]);
}
// This I mention later
console.log("LoadJson feeds.length: " + feeds.length);
}
});
}
console.log("LoadJson return"); // Mention later
return feeds_tmp;
}
And here is the problem I am struggling with. When I look at the console, here what I see:
LoadJson return
main_code feeds.length: 0
LoadJson feeds.length: 1
LoadJson feeds.length: 2
LoadJson feeds.length: 3
etc...
I just don't see the logic behind it! How can it first returned the function with nothing, then the main_script continues. After that, the function ALTER one by one the global variable "feeds". I suspect the anonymous function, but don't know what to do with it.
What am I trying to achive? Simple, I wanted to have function, that load JSON files from URLs. For example url[0] has 50 feeds, url[1] has 20. If everything is ok then it should return array of 70 feeds. I use this for the first time in main_script, and then in interval for update, which I call every few seconds. In this function I check, which feed is new and put it somewhere else:
function UpdateFeeds(url) {
console.log("updating...");
var feeds_tmp = LoadJsonFeeds(url);
console.log("Update feeds_tmp.length: " + feeds_tmp.length); // This is 0
for(var f_tmp = 0; f_tmp < feeds_tmp.length; f_tmp++) { // This does not happen because feeds_tmp.length = 0
for(var f = 0; f < feeds.length; f++) {
// Check what feed is new and put it somewhere else (the new one)
}
}
}
feeds = feeds_tmp; // Make all new feeds the global variable
}
But since the returned array is 0, that forloop does not happen. But it will still alter the global variable "feeds" anyway. For the main function it does not matter. In global variable the datas are in it, but I really need to find a new ones and do some work with it. But since it does not work that way, I am pretty lost.
What am I missing and how to fix this? Thank you!
Your console.log("LoadJson feeds.length: " + feeds.length); called later because its a asynchronous call , you can update this function as
function LoadJsonFeeds(urls,callback) {
var feeds_tmp = [];
// URLs can be more - for example 50 feeds from url[0] and 20 from url[1]
for(var u = 0; u < url.length; u++) {
$.getJSON(url[u], function(data) {
var allFeeds = data.Result.Items; // allFeeds without check if they are ok
for(var i = 0; i < allFeeds.length; i++) {
// Is feed ok?
if (allFeeds[i].Text != null)
{
// Some more checking, but lets say ok for this
feeds_tmp.push(allFeeds[i]);
}
// This I mention later
console.log("LoadJson feeds.length: " + feeds.length);
}
if(u==url.length.1) // to make sure all URL loaded
callback(feeds_tmp)
});
}
}
And call your function as
feeds = LoadJsonFeeds(urls,function(feeds){
console.log("main_code feeds.length: " + feeds.length);
}); // Load feeds
I am trying to render an html page that contains all of the posts that a user has received. Right now the issue I am having (shown under Way 1) is that when I call the function renderPosts after the web socket is received, only the first post in the array is rendered (the array has more than one post in it).
On the other hand, Way 2 in which I have no for loop and instead manually render each post works in that all four posts are rendered. But I need to be able to work with an arbitrary number of posts which is why I need to use the for loop.
I am using socket.io and javascript.
Way 1:
socket.on('postsToRender', function(arrayOfPostsToRender) {
renderPosts(arrayOfPostsToRender);
});
function renderPosts(arrayOfPostsToRender) {
for (var index = 0; index < arrayOfPostsToRender.length; index++) {
renderPost(arrayOfPostsToRender[index]);
}
}
function renderPost(postToRender) {
var feed = document.getElementById("feed");
var postContent = document.createTextNode(postToRender.content);
var post = document.createElement("div");
post.appendChild(postContent);
feed.appendChild(post);
}
Way 2:
socket.on('postsToRender', function(arrayOfPostsToRender) {
renderPost(arrayOfPostsToRender[0]);
renderPost(arrayOfPostsToRender[1]);
renderPost(arrayOfPostsToRender[2]);
renderPost(arrayOfPostsToRender[3]);
});
function renderPost(postToRender) {
var feed = document.getElementById("feed");
var postContent = document.createTextNode(postToRender.content);
var post = document.createElement("div");
post.appendChild(postContent);
feed.appendChild(post);
}
Try this:
function renderPosts(arrayOfPostsToRender) {
for (var index = 0; index < arrayOfPostsToRender.length; index++) {
(function(i){
renderPost(arrayOfPostsToRender[i]);
})(index);
}
}
Thanks for the help in advance.
I'm working on an practice assigment using Phonegap and Javascript. Long story short: I need to use Parse.com to store information about some Lego minifigures. The problem I'm having right now is due mostly to my inexperience in Javascript.
I'm working on letting the user add tags to the figures. The user enters them, separated by comma, and I then split the string. That's working OK.
Now, I need to add the tags that don't exist yet to my database. For this, I search for any tags with that description (using query.find) and then, if it exists, I don't create it, I just modify the relationship. If it doesn't exist, I create it and then modify the relationship.
My problem is: I can't seem to be able to access the tag description (the string) from within the success callback of query.find. I'm pretty sure it's because of the scope. Is there any proper way to access variables from withing a success callback, besides the results array?
My current code is as follows:
var Figure = Parse.Object.extend("Figure");
var Tag = Parse.Object.extend("Tag");
var nombre = $('#nombre').val();
var serie = $('#serie').val();
var figure = new Figure({"Name":nombre,"Series":serie});
var tags = $('#tags').val();
res = tags.split(","); //split the
figure.save().then(function() {
for (var i = 0; i < res.length; i++) { //for each tag
var query = new Parse.Query(Tag); //create the query.
query.equalTo("Description", res[i]);
query.find( {//execute query
success: function(results, res[i]) {
if (results.length > 0){ //if there are results.
var tag = results[0]; //get the tag
var relation_tag = tag.relation("figures"); //get the relation
relation_tag.add(figure); //add figure to relation
tag.save();
}
else { //if there are no results, the tag does not exist.
new_tag = new Tag({"Description":res[i]});
//ABOVE THIS LINE: res[i] is always undefined.
var relation_tag = new_tag.relation("figures"); //get the relation
relation_tag.add(figure); //add the figure
new_tag.save();
}
},
//error with query
error: function() {
alert("ERROR");
}
});
}
}, function(error) {
alert("No se pudo guardar la figura");
});
In the success callback, res[i] always is undefined, I assume that it's because of the scope.
This is a very common problem in async Javascript programming. You are doing something like this:
for (var i = 0; i < array.length; i++) {
anAsyncFunction(function(result) { // inner function
doSomethingWith(array[i]);
}
}
The problem is that in Javascript functions store outer variables by reference and not by value, which means that a function looks up the value of a variable from an outer scope, when it is executed and not when it is defined. Since the code is async the the inner function is called after the for loop completed and at this point we have i === array.length, so array[i] === array[array.length] === undefined.
To avoid this you can use an immediately invoked function expression (IIFE, pronounced "iffy"):
for (var i = 0; i < array.length; i++) {
anAsyncFunction((function(j) { // IIFE
return function innerFunction(result) { // inner function
doSomethingWith(array[j]); // j instead of i
}
})(i); // passing "value of i"
}
Because the IIFE is invoked immediately, the current value is of i is passed and stored into j and when the inner function executes it uses the correct value.
So in your case this should work:
success: (function(j) { // IIFE
return function(results) {
if (results.length > 0) {
var tag = results[0];
var relation_tag = tag.relation("figures");
relation_tag.add(figure);
tag.save();
}
else { //if there are no results, the tag does not exist.
new_tag = new Tag({"Description":res[j]}); // j instead of i
var relation_tag = new_tag.relation("figures");
relation_tag.add(figure);
new_tag.save();
}
}
})(i) // pass "value of i"
If you prefer, you can also pass the description itself instead of just the index to the IIFE (I think I would do it that way):
success: (function(description) { // IIFE
return function(results) {
if (results.length > 0) {
var tag = results[0];
var relation_tag = tag.relation("figures");
relation_tag.add(figure);
tag.save();
}
else { //if there are no results, the tag does not exist.
new_tag = new Tag({"Description":description}); // description
var relation_tag = new_tag.relation("figures");
relation_tag.add(figure);
new_tag.save();
}
}
})(res[i]) // pass description
var Tag = Parse.Object.extend("Tag");
var query = new Parse.Query(Tag);
Using javascript I get list of facebook friends though it only returns name and id now, but I need to get the picture of each user. I try to loop through the response and then try to call the api to get picture, but due to it's async call I can't associate the returned picture with the index of the friend in the array. *this is kinda a problem that I've had with asynchronous programming in general, is there a standard pattern for this?
Example.
FB.api('me/friends', function(response) {
if(response.error == null){
var friendsSale = response.data;
var len = friendsSale.length;
for(var x=0; x<len; x++){
FB.api(friendsSale[x].id+'/picture', function(response) {
//x no longer is the same x as the initial call, and I can't pass in the orignal array object into the FB.api function to return as part of the response... or can I?
friendsSale[x].pictureUrl = response;
});
}
}
//Then how do I know when I have all the pictures set so I can then set datamodle with the complete friend array?
m.friends(friendsSale);
}
});
Yes, there is a pattern for this: a Closure
...
var len = friendsSale.length;
for (var i = 0; i < len; i++) {
(function() {
var j = i;
FB.api(friendsSale[i].id+'/picture', function(response) {
friendsSale[j].pictureUrl = response;
});
})();
}
To know when all all calls have returned you can simply keep a counter of returned calls, e.g.
...
var len = friendsSale.length;
var returnedCallsCounter = 0;
for (var i = 0; i < len; i++) {
(function() {
var j = i;
FB.api(friendsSale[i].id+'/picture', function(response) {
friendsSale[j].pictureUrl = response;
// Track number of returned calls
returnedCallsCounter++;
// Check if all calls have returned
if (returnedCallsCounter == len) {
m.friends(friendsSale);
}
});
})();
}
Simple solution for you :
All you have to do is query this :
https://graph.facebook.com/user_id/picture
and you will get the users profile picture. For example :
Querying https://graph.facebook.com/4/picture (with no access token BTW - try it in chrome pron incognito mode) :
<img src="https://graph.facebook.com/4/picture">
will yeild this smiling face :
Now you know Marks fbid :P