callbacks when trying to create an array from asynchronous calls in javascript - javascript

I am trying to create an array from an asynchronous get request using a function that uses a for loop in order to pass a parameter in the get request.
var loadRarity = function () {
var rarities = [];
for (var i =0; i < deck.length; i++) {
Card.get({cardName: deck[i].card_name}, function(data) {
rarities.push(data.data[0].rarity);
console.log(rarities); //20 instances where the array is being populated
});
console.log(rarities);// result :20x [] empty array
}
return rarities;
};
var raritiesArray = loadRarity();
console.log(raritiesArray); //empty array
I can't figure out how to use the callback to make this work.

An option is to increment a counter to check if you are on the last callback an then do any needed operation in that last callback
var loadRarity = function () {
var rarities = [];
var counter = 0; // initialize counter
for (var i =0; i < deck.length; i++) {
Card.get({cardName: deck[i].card_name}, function(data) {
counter += 1; //increment counter
rarities.push(data.data[0].rarity);
console.log(rarities); //20 instances where the array is being populated
if(counter == deck.length){ //if true you are in the last callback
console.log(raritiesArray); // array with all the elements
}
});
}
return rarities;
};
var raritiesArray = loadRarity();

Waiting for all this async stuff to happen, your code that needs to use the result should be in its own callback, which runs when the result is available. For example:
var loadRarity = function(cb) {
var rarities = [],
counter = 0,
done = function(){
if(counter++ === deck.length - 1){
cb(rarities);
}
};
for (var i =0; i < deck.length; i++) {
Card.get({cardName: deck[i].card_name}, function(data) {
rarities.push(data.data[0].rarity);
done();
});
}
};
loadRarity(function(completedRarities){
console.log(completedRarities);
});
Sample (using an image onload fn to simulate your asysnc call): http://codepen.io/RwwL/pen/VeeEBR?editors=001

Related

Deferred objects with multiple getJSON calls that depend on each other

I am making 3 get JSON calls in myFunction() that gets called on a button click. 2 of these getJSONs depend on each other for their execution. It basically parses through 10 web pages and collects some data. With this data it will go to another page and collect some other data. I want to display "DONE" at the end of myFunction so the user knows that we have finally got all data and the search operation is complete. However these calls, I think, are asynchronous, so I use deferred objects. But even though I pass all calls to $.when.apply(call1,call2,call3), it displays the "DONE" before any data is printed on the console. Once it prints "DONE", then it starts to print the results. How can I modify my code so that I would be able to display "DONE" only when myFunction has ran completely for all 10 pages and has printed all data on the console.
var call1 = [];
var call2 = [];
var call3 = [];
function myFunction() {
data3 = [];
url = ''; // some URL here
call3.push($.getJSON(url, function(data4) {
data3 = data4;
}));
for (var page = 1; page < 10; page++) {
(function(page) {
url = 'http://example.com/' + page;
call1.push($.getJSON(url, function(data1) {
for (var num = 0; num < data1.standings.results.length; num++) {
(function(num) {
url = 'http://example.com/' + data1.entry[num];
call2.push($.getJSON(url, function(data2) {
for (var i = 0; i < 15; i++) {
(function(i) {
console.log(data3.ele[(data2.p[i].element) - 1].x);
return;
}
})(i);
}
})
);
})(num);
}
})
);
})(page);
};
$.when.apply(call1, call2, call3).then(function() {
console.log("DONE");
});
}
I was finally able to solve this problem. As mentioned in the comments, we need to chain various promises and the function structure should match the structure of the when command. So, in the function as call1 was pushed first, I need to call the when command for call1 and then nest the subsequent commands and so on.
var call1 = [];
var call2 = [];
var call3 = [];
function myFunction() {
data3 = [];
url = ''; // some URL here
call3.push($.getJSON(url, function(data4) {
data3 = data4;
}));
for (var page = 1; page < 10; page++) {
(function(page) {
url = 'http://example.com/' + page;
call1.push($.getJSON(url, function(data1) {
for (var num = 0; num < data1.standings.results.length; num++) {
(function(num) {
url = 'http://example.com/' + data1.entry[num];
call2.push($.getJSON(url, function(data2) {
for (var i = 0; i < 15; i++) {
(function(i) {
console.log(data3.ele[(data2.p[i].element) - 1].x);
return;
}
})(i);
}
})
);
})(num);
}
})
);
})(page);
};
$.when.apply($, call1).then(function(){
$.when.apply($, call2).then(function(){
document.getElementsByName('output')[0].value+="Search Completed"+'\r\n';
});
});
}

How to run a function once all the ajax calls have been done

Say I have this function :
function testAjax(url) {
return $.ajax({
url: url,
type: 'GET'
});
}
Now I have a number of nodes (I am using D3), I wish to loop through. Each one may or may not have some files associated. To find out if it does I get the url on the chosen node, inspect the returned data, if it has a file/files I add it to the array of files. I then wish to log the file array only after I have gone through each node and inspected it to see if it has files.
Rest of the code is similar to this :
var allFiles = [];
nodes.each(function(d) {
testAjax(d.url)
.success(function(data) {
if (data.files) {
if (data.files.length > 0) {
for (var i = 0; i < data.files.length; i++) {
allFiles.push(data.files[i])
}
}
}
})
})
//Here is where I want to log/use (call a function passing the files array as an argument) the array of files after its been completed.
Create array of promises and use $.when() to be resolved when full array is resolved
var allFiles = [];
var promises = [];
nodes.each(function(d) {
// reference to promise
var req= testAjax(d.url)
.success(function(data) {
if (data.files) {
if (data.files.length > 0) {
for (var i = 0; i < data.files.length; i++) {
allFiles.push(data.files[i])
}
}
}
})
});
// push to array
promises.push(req);
});
$.when.apply(null, promises).then(function(){
// all promises have been resolved here
}).fail(function(){
alert('Opps ... something went wrong with one of the requests')
})
You can use a check in the success callback to see if you have reached the end of the nodes array:
var allFiles = [];
nodes.each(function(i, d) {
testAjax(d.url)
.success(function(data) {
if (data.files) {
if (data.files.length > 0) {
for (var i = 0; i < data.files.length; i++) {
allFiles.push(data.files[i])
}
}
}
// Check if the length-1 is equal to the index
if (i == nodes.length-1) doneLooping();
})
})
doneLooping() should be called as the last callback completes.
Here is a useful page on the .each() function.
Please try something like
var allFiles = [];
var totalNodes = 0,
totalSuccessResponses = 0;
nodes.each(function(d) {
totalNodes++;
testAjax(d.url)
.success(function(data) {
totalSuccessResponses++;
if (data.files) {
if (data.files.length > 0) {
for (var i = 0; i < data.files.length; i++) {
allFiles.push(data.files[i])
}
}
}
if(totalNodes == totalSuccessResponses) {
// callback function()
// is excuted after all success responses received
}
})
})
I would use $.when, to be sure all promises have been resolved before perform any treatment.
Here is an example (not directly related to you code, but using mocked calls):
var test = function (i) {
var result = $.Deferred();
setTimeout(function () {
console.log(i + ": done");
result.resolve(i);
}, 1000 + Math.floor(Math.random() * 2000));
return result;
};
// with apply to allow the possible usage of $.map on an array of objects, and $.then on each promises
$.when.apply($, [test(1), test(2), test(3)]).done(function (i1, i2, i3) {
console.log(i1 + " " + i2 + " " + i3 + " are done");
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
If you want to perform post treatment on you ajax call results before invoking when you can combine the above approach with $.then and $.map

Returning the array before the function is complete & Angular JS

I am trying to return the array 'self.results' with all the arrays pushed in, which is after the self.yelpResults is completed. I want to use the returned array in another function. For now, self.parsedYelpArray is suppose to accept that array.
I am having trouble getting the self.results return all the arrays that are being pushed in. Instead, it asynchronously push the original empty array into the self.parsedYelpArray function.
How do I resolve this?
This is the code in my controller:
self.MapRouteArray = CompileMapArray.compileRoutes(data);
self.yelpResults = CompileYelpResults.compileYelp(self.MapRouteArray);
self.parsedYelpArray = ParsingYelpResults.parsingData(self.yelpResults);
And, these are the relevant services:
.service('CompileMapArray', function () {
var self = this;
self.MapRouteArray = [];
self.compileRoutes = function (data) {
for (var i = 0; i < data.response.route[0].leg[0].maneuver.length; i += 2) {
self.MapRouteArray.push(data.response.route[0].leg[0].maneuver[i].position.latitude + ',' + data.response.route[0].leg[0].maneuver[i].position.longitude);
}
return self.MapRouteArray;
};
})
.service('CompileYelpResults', function (YelpResource) {
var self = this;
self.results = [];
self.compileYelp = function (mapArray) {
for (var j = 0; j < mapArray.length; j++) {
YelpResource.getListings({term: self.yelpSearch, ll: mapArray[0]}, function (response) {
self.results.push(response.businesses);
console.log(self.results);
});
}
return self.results;
};
})
.service('ParsingYelpResults', function () {
var self = this;
self.parsingData = function (results) {
console.log(results);
};
});
You are trying to return from an asynchronous function; you'll always get unreliable results from that, you need to pass in a callback function that handles whatever operation you want at the end of your async... Like:
.service('CompileYelpResults', function (YelpResource) {
var self = this;
self.results = [];
self.compileYelp = function (mapArray, callbackFn) {
for (var j = 0; j < mapArray.length; j++) {
YelpResource.getListings({term: self.yelpSearch, ll: mapArray[0]}, function (response) {
self.results.push(response.businesses);
console.log(self.results);
});
}
callbackFn(self.results);
};
});
Then call the function with a callback function like so:
var parsed = CompileYelpResults.compileYelp(self.MapRouteArray, function(result) {
console.log(result);
});
This goes for all your asynchronous functions.
Relating to your comment the callback function you pass as second parameter to compileYelp takes the place of parsingData, so whatever processing you want to do with the results will be in the body of the callback function. It gives extra advantage in that you can use the results whichever way you like. For example.
var logged = CompileYelpResults.compileYelp(self.MapRouteArray, function(result) {
console.log(result);
});
var stringified = CompileYelpResults.compileYelp(self.MapRouteArray, function(result) {
JSON.stringify(result);
});

How to call function after API gets are finished inside of for loop?

Below is a for loop which will run a max of time times, Inside of that for loop I make a GET call to return some data that needs to be added to my obj object.
I need some way to tell when all 3 GETS are finished as well as the for loop before calling the TagFactory.buildSavedView(obj) line. Thoughts?
for (var i = 0; i < termIDs.length; i++) {
ApiFactory.getTagData(tickers[i], termIDs[i]).then(function(data) {
singleTagArray.push(data.data.tickers);
var updatedTag = TagFactory.renderDirections(singleTagArray, null, period);
newTagObject = updatedTag[0];
tags.push(newTagObject);
finishObjSetup(tags);
console.log('viewHeaderDirect > obj: ', obj);
});
}
TagFactory.buildSavedView(obj);
vm.loadSavedModal = false;
You need to use $q.all, but creating a promise array and pass it to $q.all that will execute its .then only when all the promises gets executed.
Code
var promises = [];
for (var i = 0; i < termIDs.length; i++) {
var promise = ApiFactory.getTagData(tickers[i], termIDs[i]).then(function(data) {
singleTagArray.push(data.data.tickers);
var updatedTag = TagFactory.renderDirections(singleTagArray, null, period);
newTagObject = updatedTag[0];
tags.push(newTagObject);
finishObjSetup(tags);
console.log('viewHeaderDirect > obj: ', obj);
});
promise.push(promise); //creating promise array.
}
$q.all(promise).then(function(){
//here the call will goes after all calls completed.
})
You could use a simple counter:
var y = 0;
for (var i = 0; i < termIDs.length; i++) {
ApiFactory.getTagData(tickers[i], termIDs[i]).then(function (data) {
y++;
singleTagArray.push(data.data.tickers);
var updatedTag = TagFactory.renderDirections(singleTagArray, null, period);
newTagObject = updatedTag[0];
tags.push(newTagObject);
finishObjSetup(tags);
console.log('viewHeaderDirect > obj: ', obj);
if (y === termIDs.length) {
TagFactory.buildSavedView(obj);
vm.loadSavedModal = false;
}
});
}

returning a value after for loops

So, I have been trying for the past few hours to get an result out of a function after performing some for loops :
Cluster.prototype.initiate_api_data_fetching = function(username) {
var self = this,
object = [];
return self.initiate_available_market_search(username, function(data_object){
var json_obj = JSON.parse(data_object);
for(var obj_key in json_obj) {
for (var i = json_obj[obj_key].length - 1; i >= 0; i--) {
self.initiate_market_items_data_fetching(username, json_obj[obj_key][i].site, function(data_obj){
var json_object = JSON.parse(data_obj);
for(var data_key in json_object) {
for (var j = json_object[data_key].length - 1; j >= 0; j--) {
object.push(json_object[data_key][j]);
/*log(object);*/
};
};
log(object);
});
};
};
});
};
Making abstraction of all the variables and other things that make no sense to you readers, I would just like to know how can I return the object array with the data that I\m pushing in it. Everything is fine if I\m logging where the /*log(object);*/ is, but if I want to see what the object contains at the end of the function, I get an empty array.
I suggest you add a callback to your main function and call it when done..
Cluster.prototype.initiate_api_data_fetching = function (username, callback) {
var self = this,
object = [];
return self.initiate_available_market_search(username, function (data_object) {
var json_obj = JSON.parse(data_object)
, counter = 0;
function done() {
counter -= 1;
if (counter === 0) {
callback(object);
}
}
for (var obj_key in json_obj) {
if (!json_obj.hasOwnProperty(obj_key)) { continue; }
for (var i = json_obj[obj_key].length - 1; i >= 0; i--) {
counter += 1;
self.initiate_market_items_data_fetching(username, json_obj[obj_key][i].site, function (data_obj) {
var json_object = JSON.parse(data_obj);
for (var data_key in json_object) {
if (!json_object.hasOwnProperty(data_key)) { continue; }
for (var j = json_object[data_key].length - 1; j >= 0; j--) {
object.push(json_object[data_key][j]);
/*log(object);*/
}
}
done();
});
}
}
});
};
PS. 1 assumption is that initiate_api_data_fetching is async.
PS. 2 Follow the advice from the commenters above to improve your code. I answered your immediate question by showing you how to synchronise async calls, but don't stop there.

Categories

Resources