Populating array with response from nested AJAX calls - javascript

I have an array that I would like to fill with responses from AJAX calls like so:
var dict = [];
function fillArray(){
$.post('getUsersOnline.php', function(phpReturnVal){
// ...
for(var i = 0; i < phpReturnVal.length; i++){
$.get("https://api.twitch.tv/kraken/streams" , function(data){
dict[data.key] = data;
});
});
}
function doStuff(){
// dict is empty or undefined here
}
How would I fill dict with objects so that I could retrieve them inside doStuff()? Currently, I am able to insert stuff into dict but when I try accessing dict outside the fillArray() function, I get an empty dict variable since I'm assuming the GET call is asynchronous and doesn't happen until after all the JS code has executed...

So, dict is an object that has no push method. You'd need dict=[]; If you had to have {}, then you'd need key:value pairs to populate it, such as:
dict[key] = value;

You are going to have to keep track of the number of calls that you are doing in that for loop and fire a callback function once they are all complete. I'm not totally confident about your current solution, with calling an indefinite amount of ajax requests, but I also don't fully understand the scope of your problem or the server that you're talking to.
So basically you will have to do something like this with what you have currently:
var dict = [],
requestsCompleted = 0;
function dictFilled() {
// do something with your dict variable;
}
function fillArray(){
$.post('getUsersOnline.php', function(phpReturnVal){
// ...
for(var i = 0; i < phpReturnVal.length; i++){
$.get("https://api.twitch.tv/kraken/streams" , function(data){
dict[data.key] = data;
requestsCompleted++;
if (requestsCompleted === phpReturnVal.length) {
dictFilled();
}
});
});
}
This haven't been tested, but basically you will have to define a function that will have access to the array that you are filling and call it once all you asynchronous requests finish successfully. For tasks like this though I recommend you take a look at jQuery's Deferred API. There is always a chance that one of those requests will fail and your application should know what to do if that happens.

I'm assuming the GET call is asynchronous and doesn't happen until
after all the JS code has executed...
Appear correct.
Try
var dict = [];
function fillArray() {
// return jQuery promise object
return $.post('getUsersOnline.php', function(phpReturnVal){
// ...
for(var i = 0; i < phpReturnVal.length; i++) {
// call same `url` `phpReturnVal.length` times here ?,
// returning same `data` response at each request ?,
// populating, overwriting `dict` with same `data.key` property ?
$.get("https://api.twitch.tv/kraken/streams" , function(data) {
dict[data.key] = data;
});
}; // note closing `}` at `for` loop
// return value
return dict
});
}
function doStuff(data) {
// `data`:`dict`, promise value returned from `fillArray`
console.log(data);
}
fillArray().then(doStuff, function error(jqxhr, textStatus, errorThrown) {
console.log(textStatus, errorThrown)
});
var arr = ["a", "b", "c"];
var response = {
"a": 1,
"b": 2,
"c": 3
};
var obj = {};
var dict = [];
function fillArray() {
return $.when(arr).then(function(phpReturnVal) {
for (var i = 0; i < phpReturnVal.length; i++) {
// return same `response` here ?
$.when(response).then(function(data) {
dict[arr[i]] = data;
});
};
return dict
});
}
function doStuff(data) {
console.log(data)
}
fillArray().then(doStuff, function error(err) {
console.log(err)
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js">
</script>

Related

Can I get a value returned from a $.Deferred() that can be used in the display?

I am using the $.Deferred method to try and get a function to load some Json, work on the results, then pass the value back to another function that will use the result (in this case I expect an integer). For some reason I can get this integer displayed using either alert or console.log functions, but when it comes to using it as the return value of the actual function the code falls over and returns undefined.
For reference here's my code:
function NewValue(){
var dataPromise = GetDataFromJson();
dataPromise.done(function(data){
//note both console.log(data) and alert(data) deliver the correct result here
return data;
});
}
function GetDataFromJson() {
var jsonData;
var deferred = $.Deferred();
d3.json("http://localhost:8000/pipeline.json", function(dataFromServer){
jsonData = dataFromServer;
headers = ["Won"];
myTotal = 0;
chunks = (headers.map(function(priceRange) {
return jsonData.map(function(d) {
return {y: +d[priceRange]};
});
}));
var myTarget = 10000000;
chunks.forEach( function (arrayItem)
{
var l = 12;
for(var i = 0; i < l; i++) {
myTotal += arrayItem[i].y;
};
});
myTotal = myTotal/myTarget*100;
deferred.resolve(myTotal);
});
return deferred.promise();
}
Is it ever possible to return the value from GetDataFromJson() and use it, or will I only ever be able to log it to the console?
EDIT after reading through the answer the only way to do this is to call the gauge update function within my GetDataFromJson call - all these promises were seemingly pointless distractions from just passing the right info across:
function GetDataFromJson(f) {
var jsonData;
var deferred = $.Deferred();
d3.json("http://localhost:8000/pipeline.json", function(dataFromServer){
jsonData = dataFromServer;
if( f == "Current"){
console.log('c');
headers = ["Won"]
} else if( f == "Projected"){
console.log('p');
headers = ["Won", "Prospecting", "Qualifying", "Demonstrating", "Negotiating"]
}
else {
alert('An error has occured')
};
myTotal = 0;
chunks = (headers.map(function(priceRange) {
return jsonData.map(function(d) {
return {y: +d[priceRange]};
});
}));
var myTarget = 10000000;
chunks.forEach( function (arrayItem)
{
var l = 12;
for(var i = 0; i < l; i++) {
myTotal += arrayItem[i].y;
};
});
myTotal = myTotal/myTarget*100;
gauge5.update(myTotal);
deferred.resolve(myTotal);
});
return true;
}
Now I call GetDataFromJson() directly from a click on a radio button, and pass either "Current" or "Projected" inside that call so the right data is shown. Feels stupid that I've wasted a week studying this, but hopefully this answer will help others. You can't extract data from these promises but you can call another function (in my case gauge5.update) and send it the value you want to use.
You are returning 'data' inside callback, not in function NewValue. NewValue should return promise and caller of NewValue should assign a callback on completion of this promise.
function NewValue(){
var defer = $.Deferred();
var dataPromise = GetDataFromJson();
dataPromise.done(function(data){
//note both console.log(data) and alert(data) deliver the correct result here
deferred.resolve(data);
});
return deferred.promise();
}
NewValue()
.done(function(data){
// do whatever
})
I am assuming you want to process the data further in NewValue function, else why can't you directly call GetDataFromJson?

Parse.Cloud.job promise not working

What I am trying to do here are:
Remove all contents in a class first, because every day the events.json file will be updated. I have my first question here: is there a better way to remove all contents from a database class on Parse?
Then I will send a request to get the events.json and store "name" and "id" of the result into a 2D array.
Then I will send multiple requests to get json files of each "name" and "id" pairs.
Finally, I will store the event detail into database. (one event per row) But now my code will terminate before it downloaded the json files.
Code:
function newLst(results) {
var event = Parse.Object.extend("event");
for (var i = 0; i < results.length; i++){
Parse.Cloud.httpRequest({
url: 'https://api.example.com/events/'+ results[i].name +'/'+ results[i].id +'.json',
success: function(newLst) {
var newJson = JSON.parse(newLst.text);
var newEvent = new event();
newEvent.set("eventId",newJson.data.id);
newEvent.set("eventName",newJson.data.title);
newEvent.save(null, {
success: function(newEvent) {
alert('New object created with objectId: ' + newEvent.id);
},
error: function(newEvent, error) {
alert('Failed to create new object, with error code: ' + error.message);
}
});
},
error: function(newLst) {
}
});
}
};
Parse.Cloud.job("getevent", function(request, status) {
var event = Parse.Object.extend("event");
var query = new Parse.Query(event);
query.notEqualTo("objectId", "lol");
query.limit(1000);
query.find({
success: function(results) {
for (var i = 0; i < results.length; i++) {
var myObject = results[i];
myObject.destroy({
success: function(myObject) {
},
error: function(myObject, error) {
}
});
}
},
error: function(error) {
alert("Error: " + error.code + " " + error.message);
}
});
var params = { url: 'https://api.example.com/events.json'};
Parse.Cloud.httpRequest(params).then(function(httpResponse) {
var results = [];
var jsonobj = JSON.parse(httpResponse.text);
for (var i = 0; i < jsonobj.data.length; i++) {
var tmp2D = {"name":"id"}
tmp2D.name = [jsonobj.data[i].name];
tmp2D.id = [jsonobj.data[i].id];
results.push(tmp2D);
}
newLst(results);
}).then(function() {
status.success("run job");
}, function(error) {
status.error(error);
});
});
I think my original answer is correct as a standalone. Rather than make it unreadable with the additional code, here it is made very specific to your edit.
The key is to eliminate passed callback functions. Everything below uses promises. Another key idea is decompose the activities into logical chunks.
A couple of caveats: (1) There's a lot of code there, and the chances that either your code is mistaken or mine is are still high, but this should communicate the gist of a better design. (2) We're doing enough work in these functions that we might bump into a parse-imposed timeout. Start out by testing all this with small counts.
Start with your question about destroying all instances of class...
// return a promise to destroy all instances of the "event" class
function destroyEvents() {
// is your event class really named with lowercase? uppercase is conventional
var query = new Parse.Query("event");
query.notEqualTo("objectId", "lol"); // doing this because the OP code did it. not sure why
query.limit(1000);
return query.find().then(function(results) {
return Parse.Object.destroyAll(results);
});
}
Next, get remote events and format them as simple JSON. See the comment. I'm pretty sure your idea of a "2D array" was ill-advised, but I may be misunderstanding your data...
// return a promise to fetch remote events and format them as an array of objects
//
// note - this differs from the OP data. this will evaluate to:
// [ { "name":"someName0", id:"someId0" }, { "name":"someName1", id:"someId1" }, ...]
//
// original code was producing:
// [ { "name":["someName0"], id:["someId0"] }, { "name":["someName1"], id:["someId1"] }, ...]
//
function fetchRemoteEvents() {
var params = { url: 'https://api.example.com/events.json'};
return Parse.Cloud.httpRequest(params).then(function(httpResponse) {
var results = [];
var remoteEvents = JSON.parse(httpResponse.text).data;
for (var i = 0; i < remoteEvents.length; i++) {
var remoteEvent = { "name": remoteEvents[i].name, "id": remoteEvents[i].id };
results.push(remoteEvent);
}
return results;
});
}
Please double check all of my work above regarding the format (e.g. response.text, JSON.parse().data, etc).
Its too easy to get confused when you mix callbacks and promises, and even worse when you're generating promises in a loop. Here again, we break out a simple operation, to create a single parse.com object based on one of the single remote events we got in the function above...
// return a promise to create a new native event based on a remoteEvent
function nativeEventFromRemoteEvent(remoteEvent) {
var url = 'https://api.example.com/events/'+ remoteEvent.name +'/'+ remoteEvent.id +'.json';
return Parse.Cloud.httpRequest({ url:url }).then(function(response) {
var eventDetail = JSON.parse(response.text).data;
var Event = Parse.Object.extend("event");
var event = new Event();
event.set("eventId", eventDetail.id);
event.set("eventName", eventDetail.title);
return event.save();
});
}
Finally, we can bring it together in a job that is simple to read, certain to do things in the desired order, and certain to call success() when (and only when) it finishes successfully...
// the parse job removes all events, fetches remote data that describe events
// then builds events from those descriptions
Parse.Cloud.job("getevent", function(request, status) {
destroyEvents().then(function() {
return fetchRemoteEvents();
}).then(function(remoteEvents) {
var newEventPromises = [];
for (var i = 0; i < remoteEvents.length; i++) {
var remoteEvent = remoteEvents[i];
newEventPromises.push(nativeEventFromRemoteEvent(remoteEvent));
}
return Parse.Promise.when(newEventPromises);
}).then(function() {
status.success("run job");
}, function(error) {
status.error(error);
});
});
The posted code does just one http request so there's no need for an array of promises or the invocation of Promise.when(). The rest of what might be happening is obscured by mixing the callback parameters to httpRequest with the promises and the assignment inside the push.
Here's a clarified rewrite:
Parse.Cloud.job("getevent", function(request, status) {
var promises = [];
var params = { url: 'https://api.example.com'};
Parse.Cloud.httpRequest(params).then(function(httpResponse) {
var results = [];
var jsonobj = JSON.parse(httpResponse.text);
for (var i = 0; i < jsonobj.data.length; i++) {
// some code
}
}).then(function() {
status.success("run job");
}, function(error) {
status.error(error);
});
});
But there's a very strong caveat here: this works only if ("// some code") that appears in your original post doesn't itself try to do any asynch work, database or otherwise.
Lets say you do need to do asynch work in that loop. Move that work to a promise-returning function collect those in an array, and then use Promise.when(). e.g....
// return a promise to look up some object, change it and save it...
function findChangeSave(someJSON) {
var query = new Parse.Query("SomeClass");
query.equalTo("someAttribute", someJSON.lookupAttribute);
return query.first().then(function(object) {
object.set("someOtherAttribute", someJSON.otherAttribute);
return object.save();
});
}
Then, in your loop...
var jsonobj = JSON.parse(httpResponse.text);
var promises = [];
for (var i = 0; i < jsonobj.data.length; i++) {
// some code, which is really:
var someJSON = jsonobj.data[i];
promises.push(findChangeSave(someJSON));
}
return Parse.Promise.when(promises);

$.getJson from X files by a LOOP and STORE each in a VARIABLE using JAVASCRIPT

I need to $.getJSON from files by using a loop on Javascript:
///////JS.serial =[156,256] but i can have more than 2 values//
var Electricity = {
refreshJson : function() {
for(var i=0; i<JS.nbre_serials;i++)
url = 'json/kw/kw_'+JS.serials[i]+'.json';
$.getJSON(url, function(data){
// ???
Electricity.processConsumption(data_156,data_256);
});
},
I would like store each object return in a variable (i.e. var datas_156 ).
You can use $.when, so you can call Electricity.processConsumption when all the results are ready. That is possible because $.getJSON returns a promise compatible object.
refreshJson : function() {
var promises = [];
for(var i=0; i<JS.nbre_serials;i++){
var url = 'json/kw/kw_'+JS.serials[i]+'.json';
promises.push($.getJSON(url));
}
$.when.apply($, promises)
.done(function(data_156, data_256){
Electricity.processConsumption(data_156,data_256);
})
.fail(function() {
//handle errors
});
}
In the example $.when.apply($, promises) is called that way because $.when won't accept an array of promises, but a variable number of parameters.
The same way you could call Electricity.processConsumption, so you don't have to change the code every time a new "serial" is added.
refreshJson : function() {
var promises = [];
for(var i=0; i<JS.nbre_serials;i++){
var url = 'json/kw/kw_'+JS.serials[i]+'.json';
promises.push($.getJSON(url));
}
$.when.apply($, promises)
.done(function(){
Electricity.processConsumption.apply(Electricity,arguments);
})
.fail(function() {
//handle errors
});
}
And your processConsumption function might be something like:
//somewhere in the Electricity class
processConsumption: function(){
var data_156 = arguments[0];
var data_256 = arguments[1];
//do stuff
}

How to pull JSON data from two different sources?

I was wondering if there is a way to pull and use JSON data from two different sources. Currently, the code looks like this:
//JSON1
$.getJSON('url1',function(data){
$.each(data,function(key,val){
//code
});
});
//JSON2
$.getJSON('url2',function(data){
$.each(data,function(key,val){
//code
});
});
When I do this, i seems that variables created from one JSON function aren't available in the other one, which makes it hard for them to be useful together.
Is there a better way to have these two work together?
This function takes an array of urls and a callback as parameters:
function getMultiJSON(urlList,callback) {
var respList = {};
var doneCount = 0;
for(var x = 0; x < urlList.length; x++) {
(function(url){
$.getJSON(url,function(data){
respList[url] = data;
doneCount++;
if(doneCount === urlList.length) {
callback(respList);
}
});
})(urlList[x]);
}
}
You would use it like this:
getMultiJSON(['url1','url2'],function(response) {
// in this case response would have 2 properties,
//
// response.url1 data for url1
// response.url2 data for url2
// continue logic here
});
You might want to add a timeout as the function will never call your handler should any of the URLs fail to load
Variable declared within the functions using var (or blocks, using let) are not available outside of the functions (or blocks).
$.getJSON('url1',function(data){
$.each(data,function(key,val){
var only_accessible_here = key;
});
});
So if you want variables that are accessible outside the scope of the function they are declared in, you need to declare them outside of the function they are used in.
var combined_stuff = ''
$.getJSON('url1',function(data){
$.each(data,function(key,val){
combined_stuff += val;
});
});
//JSON2
$.getJSON('url2',function(data){
$.each(data,function(key,val){
combined_stuff += val;
});
});
As Marc B says, there is no way to know which order the combined_stuff variable will be updated, either by JSON1 first, or by JSON2 first, or by only one, if one of the getJSON calls fail, or by neither if both fail.
If the order of updating is important, call the one you want to use second in the function of the one you want to call first.
var combined_stuff = ''
$.getJSON('url1',function(data){
$.each(data,function(key,val){
combined_stuff += val;
//JSON2
$.getJSON('url2',function(data){
$.each(data,function(key,val){
combined_stuff += val;
});
});
});
});
Easily using the open source project jinqJs (http://www.jinqJs.com)
var data1 = jinqJs().from('http://....').select();
var data2 = jinqJs().from('http://....').select();
var result = jinqJs().from(data1, data2).select();
The example does a sync call, you can do an async call by doing something like this:
var data1 = null;
jinqJs().from('http://....', function(self){ data1 = self.select(); });
Result will contain both results combined.
If you control the endpoint, you could make it return all of the data you want in one shot. Then your data would look like:
{
"url1_data": url1_json_data,
"url2_data": url2_json_data
}
If you still have 2 endpoints you need to hit, you can pass the result of your first ajax call to the second function (but this makes your 2 ajax calls synchronous):
function getJson1(){
$.getJSON('url1',function(data){
getJson2(data);
});
}
function getJson2(json1Data){
$.getJSON('url2',function(data){
//Do stuff with json1 and json2 data
});
}
getJson1();
I would recommend you to use $.when function available in jquery to execute both the methods in parallel and then take the action. See the code snipped below,
var json1 = [], json2 = [];
$.when(GetJson1(), GetJson2()).always(function () {
//this code will execute only after getjson1 and getjson2 methods are run executed
if (json1.length > 0)
{
$.each(json1,function(key,val){
//code
});
}
if (json2.length > 0)
{
$.each(json2,function(key,val){
//code
});
}
});
function GetJson1()
{
return $.ajax({
url: 'url1',
type: 'GET',
dataType: 'json',
success: function (data, textStatus, xhr) {
if (data != null) {
json1 = data;
}
},
error: function (xhr, textStatus, errorThrown) {
json1 = [];//just initialize to avoid js error
}
}
function GetJson2()
{
return $.ajax({
url: 'url2',
type: 'GET',
dataType: 'json',
success: function (data, textStatus, xhr) {
if (data != null) {
json2 = data;
}
},
error: function (xhr, textStatus, errorThrown) {
json2 = [];//just initialize to avoid js error
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
The returned data from each AJAX call are not available outside its own callback function. I'm sure there are more elegant (complex?) solutions, but a couple of simple, Occamic, solutions include global variables, or storing the received data in hidden input elements.
Within each callback function, just loop until the data from the other call is present:
function getJson1(){
$.getJSON('url1',function(data){
var d2 = '';
$('#hidden1').val(data);
while ( d2 == '' ){
//you should use a time delay here
d2 = $('#hidden2').val();
}
getJson2();
});
}
function getJson2(){
$.getJSON('url2',function(d2){
var d1 = '';
$('#hidden2').val(d2);
while ( d1 == '' ){
//you should use a time delay here
d1 = $('#hidden1').val();
}
//Do stuff with json1 and json2 data
});
}
getJson1();

Why am I not able to populate my Array object?

if (typeof response == "object") {
store = new dojo.data.ItemFileReadStore({
data : response
});
console.warn("Loaded to Store");
var itemArray = new Array();
var completed = function(items) {
for(var i = 0; i < items.length; i++) {
console.log(store.getValue(items[i],"itemlabel"));
itemArray.push(store.getValue(items[i]));
}
};
var error = function() {
console.log("Error in fetching data from Store");
};
store.fetch({onComplete: completed, onError: error});
console.warn("Item count"+ itemArray.length);
So my Item Count gives Always 0 but
console.log(store.getValue(items[i],"itemlabel"));
In the callback method the above get print the value.
So if i want to populate my itemArray what can i do?
You're printing itemArray.length before the array gets populated with any data in the completed function, because completed will be called asynchronously.
If you want to print the actual length of itemArray, you should do it after the for-loop in completed.
Hello the answer to your problem is quite easy, but first i'd recommand using
var completed = function(items) {
dojo.forEach(items, function(item) {
console.log(store.getValue(items[i],"itemlabel"));
itemArray.push(store.getValue(items[i]));
}, this);
proceedToNextStep(itemArray);
};
and then you create a function that will be used to keep on the process once the itemArray is loaded..
proceedToNextStep: function(itemArray){
// whatever you need to do
},
You could also use dojo.publish / dojo.subscribe

Categories