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
Related
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.
I really try my damndest not to ask, but i have to at this point before I tear my hair out.
By the time the js interpreter gets to this particular method, I can print it to the console no problem, it is an array of "event" objects. From FireBug I can see it, but when I try to set a loop to do anything with this array its as if it doesn't exist. I am absolutely baffled......
A few things:
I am a newbie, I have tried a for(var index in list) loop, to no avail, I have also tried a regular old for(var i = 0; i < listIn.length; i++), and I also tried to get the size of the local variable by setting var size = listIn.length.
As soon as I try to loop through it I get nothing, but I can access all the objects inside it from the FireBug console no problem. Please help, even just giving me a little hint on where I should be looking would be great.
As for the array itself, I have no problems with getting an array back from PHP in the form of: [{"Event_Id":"9", "Title":"none"}, etc etc ]
Here is my code from my main launcher JavaScript file. I will also post a sample of the JSON data that is returned. I fear that I may be overextending myself by creating a massive object in the first place called content, which is meant to hold properties such as DOM strings, settings, and common methods, but so far everything else is working.
The init() function is called when the body onload is called on the corresponding html page, and during the call to setAllEvents and setEventNavigation I am lost.
And just to add, I am trying to learn JavaScript fundamentals before I ever touch jQuery.
Thanks
var dom, S, M, currentArray, buttonArray, typesArray, topicsArray;
content = {
domElements: {},
settings: {
allContent: {},
urlList: {
allURL: "../PHP/getEventsListView.php",
typesURL: "../PHP/getTypes.php",
topicsURL: "../PHP/getTopics.php"
},
eventObjArray: [],
buttonObjArray: [],
eventTypesArray: [],
eventTopicsArray: []
},
methods: {
allCallBack: function (j) {
S.allContent = JSON.parse(j);
var list = S.allContent;
for (var index in list) {
var event = new Event(list[index]);
S.eventObjArray.push(event);
}
},
topicsCallBack: function(j) {
S.eventTopicsArray = j;
var list = JSON.parse(S.eventTopicsArray);
topicsArray = list;
M.populateTopicsDropDown(list);
},
typesCallBack: function(j) {
S.eventTypesArray = j;
var list = JSON.parse(S.eventTypesArray);
typesArray = list;
M.populateTypesDropDown(list);
},
ajax: function (url, callback) {
getAjax(url, callback);
},
testList: function (listIn) {
// test method
},
setAllEvents: function (listIn) {
// HERE IS THE PROBLEM WITH THIS ARRAY
console.log("shall we?");
for(var index in listIn) {
console.log(listIn[index]);
}
},
getAllEvents: function () {
return currentArray;
},
setAllButtons: function (listIn) {
buttonArray = listIn;
},
getAllButtons: function () {
return buttonArray;
},
setEventNavigation: function(current) {
// SAME ISSUE AS ABOVE
var l = current.length;
//console.log("length " + l);
var counter = 0;
var endIndex = l - 1;
if (current.length < 4) {
switch (l) {
case 2:
var first = current[0];
var second = current[1];
first.setNextEvent(second);
second.setPreviousEvent(first);
break;
case 3:
var first = current[0];
var second = current[1];
var third = current[2];
first.setNextEvent(second);
second.setPreviousEvent(first);
second.setNextEvent(third);
third.setPreviousEvent(second);
break;
default:
break;
}
} else {
// do something
}
},
populateTopicsDropDown: function(listTopics) {
//console.log("inside topics drop");
//console.log(listTopics);
var topicsDropDown = document.getElementById("eventTopicListBox");
for(var index in listTopics) {
var op = document.createElement("option");
op.setAttribute("id", "dd" + index);
op.innerHTML = listTopics[index].Main_Topic;
topicsDropDown.appendChild(op);
}
},
populateTypesDropDown: function(listTypes) {
//console.log("inside types drodown");
//console.log(listTypes);
var typesDropDown = document.getElementById("eventTypeListBox");
for(var index2 in listTypes) {
var op2 = document.createElement("option");
op2.setAttribute("id", "dd2" + index2);
op2.innerHTML = listTypes[index2].Main_Type;
typesDropDown.appendChild(op2);
}
}
},
init: function() {
dom = this.domElements;
S = this.settings;
M = this.methods;
currentArray = S.eventObjArray;
buttonArray = S.buttonObjArray;
topicsArray = S.eventTopicsArray;
typesArray = S.eventTypesArray;
M.ajax(S.urlList.allURL, M.allCallBack);
//var tempList = currentArray;
//console.log("temp array length: " + tempList.length);
M.setAllEvents(currentArray);
M.testList(currentArray);
M.setEventNavigation(currentArray);
//M.setEventNavigation();
M.ajax(S.urlList.topicsURL, M.topicsCallBack);
M.ajax(S.urlList.typesURL, M.typesCallBack);
}
};
The problem you have is that currentArray gets its value asynchronously, which means you are calling setAllEvents too soon. At that moment the allCallBack function has not yet been executed. That happens only after the current running code has completed (until call stack becomes emtpy), and the ajax request triggers the callback.
So you should call setAllEvents and any other code that depends on currentArray only when the Ajax call has completed.
NB: The reason that it works in the console is that by the time you request the value from the console, the ajax call has already returned the response.
Without having looked at the rest of your code, and any other problems that it might have, this solves the issue you have:
init: function() {
dom = this.domElements;
S = this.settings;
M = this.methods;
currentArray = S.eventObjArray;
buttonArray = S.buttonObjArray;
topicsArray = S.eventTopicsArray;
typesArray = S.eventTypesArray;
M.ajax(S.urlList.allURL, function (j) {
// Note that all the rest of the code is moved in this call back
// function, so that it only executes when the Ajax response is
// available:
M.allCallBack(j);
//var tempList = currentArray;
//console.log("temp array length: " + tempList.length);
M.setAllEvents(currentArray);
M.testList(currentArray);
M.setEventNavigation(currentArray);
//M.setEventNavigation();
// Note that you will need to take care with the following asynchronous
// calls as well: their effect is only available when the Ajax
// callback is triggered:
M.ajax(S.urlList.topicsURL, M.topicsCallBack); //
M.ajax(S.urlList.typesURL, M.typesCallBack);
});
}
I can't add new name and value ff. this given condition:
$.each(names, function (i, name) {
$.get('https://www.example.com/path/' + name, function (data) {
var arrNow = CSVToArray(data, ',');
allArr.push(arrNow);
counter++;
if (counter === names.length) {
for (var j = 0; j < allArr.length; j++) {
for (var k = 1; k < allArr[j].length; k++) {
//console.log(allArr[j][k][0] + ': ' + allArr[j][k][1]);
//var f = moment(allArr[j][k][0]).format('lll');
var f = allArr[j][k][0];
json.push({
"datetime": f
});
if (j == 0) {
if (json[k].datetime === allArr[0][k][0]) {
var newAtt = "water_actual";
var newValue = allArr[0][k][1];
json[k][newAtt] = newValue;
}
}
if (j == 1) {
if (json[k].datetime === allArr[1][k][0]) {
var newAtt = "rainfall_actual";
var newValue = allArr[1][k][1];
json[k][newAtt] = newValue;
}
}if (j == 2) {
if (json[k].datetime == allArr[2][k][0]) {
var newAtt = "forecast_water";
var newValue = allArr[2][k][1];
json[k][newAtt] = newValue;
}
}
}
}
};
});
});
I was able to add a new namewater_actual and its value using if statement. If the datetime from the json object matches to the array value(date and time), I'd like to add it with its specific name as stated above. But I can't seem to make it work.
Here's the fiddle.
If I may provide some general feedback: it's probably good practice to simplify your code to the minimum example that reproduces your problem. Not only can that drastically increase your chances of fixing it yourself, it also increases the odds that you'll get help here.
With that in mind, consider the basic structure of what you're trying here:
var someNames = ["foo", "bar"];
var allTheData = [{
"aardvark": true
}];
$.each(someNames, function (i, name) {
$.get('http://example.com/api/' + name, function (data) {
data.aNewProperty = 'wombat';
allTheData.push(data);
});
});
console.log(allTheData);
Here, $.each iterates through everything in someNames and then proceeds immediately to the console.log statement. For all we know, each individual API call ($.get) could take seconds, or minutes. By this time we've already tried to use the contents of allTheData, which may or may not have been modified.
To avoid this sort of thing in legacy JavaScript we can make use of the callback already provided by $.get:
$.get('http://example.com/api/' + name, function (data) {
data.aNewProperty = 'wombat';
console.log(data);
});
Inside the callback, we know for sure that the API request has already completed (although the above assumes that it succeeded, which is a whole other kettle of fish). This would output the result of each API request as the responses arrive, though not necessarily in the order you'd expect!
JavaScript's asynchronous nature tended to lead in the past to a whole lot of callbacks. With the advent of ES6 we have some more options available to us, especially promises.
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);
In my project I am calling same service for more than 2-3 times for finding distance & time for my route in javascript using cloudmode service.
I am having one polyline, I am getting each point of polyline & passing two consecutive points to service to get response from it.
What I am doing is
function showPointsRoutes(e)
{
var a = e.target.getLatLngs();
for(var i = 1 ; i < a.length ; i++)
{
var as ="http://routes.cloudmade.com/BC9A493B41014CAABB98F0471D759707/api/0.3/" + a[i-1].lat+","+a[i-1].lng+","+a[i].lat+","+a[i].lng + "/car/shortest.js?callback=getRouteResponse";
addScript(as);
}
}
function getRouteResponse(response)
{
mytimeArray.push[response.route_summary.total_time];
myDistancArray.push[response.route_summary.total_distance];
}
function addScript(url)
{
var script = document.createElement('script');
script.type="text/javascript";
script.src=url;
document.getElementsByTagName('head') [0].appendChild(script);
}
But sometimes what happens is that response for some requests comes late.
due to this I am not getting proper times & distance between points.
Can any one suggest some way so that I get values in array as sequence in which request was made.
The reason they give you different callback names is so that you can differentiate requests by using a different callback for each one. Try something like this:
Note: #Rodrigo Assis's suggestion would be better in this case since the API supports this. Code below demonstrates a way to handle this when the API forces using multiple requests.
function showPointsRoutes(e)
{
var a = e.target.getLatLngs();
for(var i = 1 ; i < a.length ; i++)
requestRoute(i, a[i-1], a[i]);
}
function requestRoute(i, a, b)
{
window["getRouteResponse" + i] = function(response)
{
mytimeArray[i] = response.route_summary.total_time;
myDistancArray[i] = response.route_summary.total_distance;
};
var as ="http://routes.cloudmade.com/BC9A493B41014CAABB98F0471D759707/api/0.3/" + a.lat+","+a.lng+","+b.lat+","+b.lng + "/car/shortest.js?callback=getRouteResponse" + i;
addScript(as);
}
function addScript(url)
{
var script = document.createElement('script');
script.type="text/javascript";
script.src=url;
document.getElementsByTagName('head') [0].appendChild(script);
}