Dynamic multiple Deferred jQuery Ajax calls - javascript

Using the Deferred pattern from jQuery http://api.jquery.com/jQuery.when/, I am trying to make multiple jsonp ajax calls and wait for the results before moving to the next step. I can accomplish this using a fixed amount of calls because I can set the number of resolved argument parameters in the ".done()" deferred object. But in my application it doesn't work because the number of calls is dynamic and always unknown.
This first simplified example works because I can set the number of args in the .done() resolved function. I know I need two because there are two calls in the .when():
$.when( $.ajax( url1 ), $.ajax( url2 ) ).done(function( a1, a2 ) {
var data = a1[ 0 ] + a2[ 0 ];
});
This is what I need but can't get it to work:
var urls = GetUrlList(); // returns array of urls to json service
var requests = []; // hold ajax request
for (i = 0; i < urls.length; i++) {
requests.push($.ajax(url[i]));
}
$.when.apply($, requests).done(function ("what goes here?") {
// Need to get the data returned from all ajax calls here
});
Thanks for any help on this!

You can use arguments, which is a special king of object holding all arguments passed to a function
$.when.apply($, requests).done(function () {
console.log(arguments); //it is an array like object which can be looped
var total = 0;
$.each(arguments, function (i, data) {
console.log(data); //data is the value returned by each of the ajax requests
total += data[0]; //if the result of the ajax request is a int value then
});
console.log(total)
});

Related

How do I store the results of multiple asynchronous $.get requests in an array?

I have a list of items being stored in an array. For each item in the array, I need to make an API request and add some of the data that comes back into another array so that I can perform operations on it later.
I think the issue is that my get request is asynchronous, and as a result the data is not necessarily loaded when I am trying to add it to the array, but I thought that's what .then was supposed to cover.
var cardRequestList = ["Scapeshift","Ghostform", "Daybreak Chaplain"]
var url = "https://api.magicthegathering.io/v1/cards?name=%22";
var cardInfo =[];
for (var cardName in cardRequestList){
var results = getCard(cardRequestList[cardName]);
cardInfo.push(results);
}
console.log(cardInfo); // results should come back here
function getCard(cardName){
var cardUrl = url.concat(cardName,"%22");
$.get(cardUrl).then(
function(data) {
var temp = [data.cards[0].name,data.cards[0].printings]
return temp;
}, function() {
alert( "$.get failed!" );
}
);
}
then() only works for the specific request it's invoked on.
To solve the issue you have you could make your requests in a loop, adding the jqXHR object returned from those calls to an array which you can then apply to $.when() to execute some other logic once all the requests have completed.
Within the requests themselves you need to add the returned data to an array as you cannot return anything from an async call.
With all that said your code would look something like this:
var cardRequestList = ["Scapeshift", "Ghostform", "Daybreak Chaplain"]
var url = "https://api.magicthegathering.io/v1/cards?name=%22";
var cardInfo = [];
var requests = cardRequestList.map(function(cardName) {
return getCard(cardName);
});
function getCard(cardName) {
var cardUrl = url.concat(cardName, "%22");
return $.get(cardUrl).then(function(data) {
cardInfo.push([data.cards[0].name, data.cards[0].printings]);
}, function() {
alert("$.get failed!");
});
}
$.when.apply($, requests).done(function() {
// all requests finished and cardInfo has been populated, place logic here...
console.log(cardInfo);
});

How To Load URL One by One With Javascript

How to touch url one by one via javascript ajax or jquery from array? Because if you touch big process php in one touch will make timeout, so how process one by one?
Example
var myurls = [
"http://example.com/grape.php",
"http://example.com/apple.php",
"http://example.com/orange.php",
"http://example.com/banana.php"];
May be if grape.php is done and then next to apple, if apple is done and then next to orange.
And then if all process finished, show alert success.
You mean this?
var myurls = [
"http://example.com/grape.php",
"http://example.com/apple.php",
"http://example.com/orange.php",
"http://example.com/banana.php"],cnt=0;
function process(data) {
console.log(data);
}
function loadUrl() {
if (cnt>=myurls.length) return;
$.get(myurls[cnt++],function(data) {
process(data);
loadUrl();
});
}
$(function() {
loadUrl();
})
Based on your question and the discussion we had in your comments, I figured that you wanted to perform AJAX calls sequentially based on an array of URLs you have.
Solution 1: Use repeated, self-referencing $.ajax() requests
This is undoubtedly the easier solution. What we have here is that we keep track of the position of the array we are in, and stop making AJAX requests when the array has been iterated through.
$(function() {
// Array of URLs
var myurls = [
"https://jsonplaceholder.typicode.com/posts/1",
"https://jsonplaceholder.typicode.com/posts/2",
"https://jsonplaceholder.typicode.com/posts/3",
"https://jsonplaceholder.typicode.com/posts/4"
];
// Iterate through array, and keep a cursor on which item we are at
var urlCount = 0,
ajaxCall = function() {
if (urlCount < myurls.length) {
console.log('Making AJAX call to url: '+myurls[urlCount]);
$.ajax({
url: myurls[urlCount]
})
.done(function(returnedData) {
console.log(returnedData);
urlCount++;
ajaxCall();
});
}
};
ajaxCall();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Solution 2: Using .then() chaining set up using a for loop
I have adapted the code provided in this answer, for your use case, since the example can be quite difficult to understand for those unfamiliar with deferred objects and returned promises.
So the trick is the following:
Set up a master deferred object
Set up a general function that will make AJAX calls for you
Loop through the array, chaining them using .then()
Kick start the first AJAX call on the master deferred object
$(function() {
// Array of URLs
var myurls = [
"https://jsonplaceholder.typicode.com/posts/1",
"https://jsonplaceholder.typicode.com/posts/2",
"https://jsonplaceholder.typicode.com/posts/3",
"https://jsonplaceholder.typicode.com/posts/4"
];
// Set up chain of AJAX requests
var d = $.Deferred(),
_d = d,
ajaxRequest = function(ajaxUrl) {
// Log in browser console that AJAX call is being made
console.log('Making AJAX call to: ' + ajaxUrl);
// Return deferred object for .then() chaining
return $.ajax({
url: ajaxUrl
});
};
// We chain each AJAX call to the next one
for (var i in myurls) {
// Use IIFE so that reference to `i` is fixed
(function(j) {
// Update _d for chaining
_d = _d.then(function() {
// The _request is a defered object returned
// So we can chain deferred methods such as .done()
var _request = ajaxRequest(myurls[j]).done(function(ajaxData) {
// Just to show that data is being returned after each call
console.log(ajaxData);
});
// Return the deferred object for chaining
return _request;
});
})(i);
}
// Kick start sequential ajax call
d.resolve();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

How to process the results of a series of AJAX requests sequentially?

I need to populate an HTML table using records retrieved via a series of AJAX
requests. The AJAX requests are all generated in a while loop. The total number of requests made is governed by a value provided by the server in a previous exchange of information. The AJAX requests need to be handled asynchronously while the results of the request need to be processed in the order in which originally requested.
The problem, of course is that the responses to the requests do not always
get answered in order. So, after reading up on jquery's Deferred/promise interface I thought I had a solution in hand. But for the life of me can't get my head around how to defer the processing (populating the table) of the results of a requests until the results of previous request(s) have been hanlded. I've found many examples on StackOverflow that get me close, but I can't seem to connect the dots to get it all working in my application.
First I tried using an array of Deferred objects, thinking that I could use indexed references to them to make the processing of one set of records dependent upon the completion of the processing of the previous set (in "as requested" order) set of data. But I couldn't figure out how to create a promise/Deferred object associated with the actual processing of the data - using .then().
var deferreds = [];
var recsPerPkt = 20;
var recRqstd = 0;
while(recsRqstd < totalRecsAvailable) {
console.log("Next record index to request: " + nextNdxRqst)
// Collect an array of the "promise" objects that $.ajax() returns for each call.
deferreds.push( $.ajax({
url: 'eventSummaryData',
type: 'get',
cache: false,
data: {StartNdxNum: nextNdxRqst, NumRecords: recsPerPkt}
}) // End $.ajax({ url: 'trainSummaryData', ...
); // End deferreds.push()
recsRqstd += recsPerPkt;
nextNdxRqst = recsRqstd;
if (deferreds.length > 1)
{
deferreds[deferreds.length - 2].then(
function (jsonData) {
if (jsonData.ok) {
// Now display the rows/records included in this packet.
displayrRecordsInTable({"rows": jsonData.rows});
}
},
function(){
$('#error-msg').text('HTTP error: ' + errorThrown);
}
);
}
}
$.when.apply(null, deferreds).then( function(){
console.log("Processing of AJAX'd data complete. ")
configureTableControls();
});
Then I found a "pattern" that chained the processing functionality using then(),
but the example didn't quite fit my exact situation and ended up having no reference to the http response data in my process-the-data handler. When I run this, the browser logs to the console, "jsonData undefined" .
var prevPromise = $.Deferred().resolve();
var recsPerPkt = 20;
var recRqstd = 0;
while(recsRqstd < totalRecsAvailable) {
prevPromise = prevPromise.then(function(){
return $.ajax({
url: 'eventSummaryData',
type: 'get',
cache: false,
data: {StartNdxNum: nextNdxRqst, NumRecords: recsPerPkt}
});
}).then(function (jsonData) {
if (jsonData.ok) {
// Now display the rows/records included in this packet.
displayTrainSummaryRows({"rows": jsonData.rows});
}
},
function(){
$('#error-msg').text('HTTP error: ' + errorThrown);
}
);
recsRqstd += recsPerPkt;
nextNdxRqst = recsRqstd;
}
So, how can I enforce the processing of AJAX requested data in the sequence in
which the requests were originally made? Thanks in advance.
I would suggest using native Promises over jQuery's deferred objects. They are much more readable, easier to understand, native to the language, and have increasing browser support (with the exception of all IE versions). To account for browser support, use a polyfill like es6-promise and you'll be set. This article does a good job of explaining the basics of promises.
Print results one by one, slower overall
Check out the section of that article I linked to, titled "Parallelism and sequencing" because that really goes in depth on how this works. Essentially you need to create a promise generator function, some sort of mapping for how many ajax requests you need to make (in this case just an array that goes up by 20 each time), and a sequence variable that holds the previous promise that was looped through.
var totalRecsAvailable = 10; //generated elsewhere apparently
var recsPerPkt = 20;
var nextNdxRqst = 0;
var recordRanges = [];
var sequence = Promise.resolve(); //initialize to empty resolved promise
//this generates a promise
function getMyData(startPosition) {
return new Promise(function(resolve,reject){
$.ajax({
type: 'GET',
dataType: 'json',
url: 'eventSummaryData',
data: {StartNdxNum: startPosition, NumRecords: recsPerPkt}
success: function(response){resolve(response);},
error: function(response){reject(response);}
});
});
}
//build out array to inform our promises what records to pull & in which order
for (var i = 0; i < totalRecsAvailable; i++) {
recordRanges.push(nextNdxRqst);
nextNdxRqst += recsPerPkt;
}
//loop through record ranges, chain promises for each one
recordRanges.forEach(function(range) {
sequence = sequence.then(function() {
return getMyData(range); // return a new Promise
}).then(function(data) {
//do stuff with the data
addToHtmlTable(data.something);
}).catch(function(error) {
//something went wrong
console.log(error);
});
});
As outlined in that article, using reduce instead of a forEach is actually a bit better, but I thought this was more clear what was happening.
Wait until all processes resolve, faster overall
For slightly faster performance, you should use Promise.all(). This takes an iterable (like an array) of promises, runs those promises asynchronously, and then saves the results to an array in the order they were passed. If one of the promises fails, the whole thing will fail and give an error. This sounds exactly like what you need. For example, you could do something like this:
var recsPerPkt = 20;
var nextNdxRqst = 0;
var totalRecsAvailable = 10; //generated elsewhere apparently
var promises = [];
//this generates a promise
function getMyData(startPosition, recordsNumber) {
return new Promise(function(resolve,reject){
$.ajax({
type: 'GET',
dataType: 'json',
url: 'eventSummaryData',
data: {StartNdxNum: startPosition, NumRecords: recordsNumber}
success: function(response){resolve(response);},
error: function(response){reject(response);}
});
});
}
//create list of promises
for (var i = 0; i < totalRecsAvailable; i++) {
promises.push(getMyData(nextNdxRqst,recsPerPkt));
nextNdxRqst += recsPerPkt;
}
//This will run once all async operations have successfully finished
Promise.all(promises).then(
function(data){
//everything successful, handle data here
//data is array of results IN ORDER they were passed
buildTable(data);
},
function(data){
//something failed, handle error here
logoutError(data);
}
);
That should set you down the right path.
I don't know of any jQuery function that does what you want, but here is a function processInOrder that will not perform your callback until all previous async ops have resolved while still allowing you to access their results
function processInOrder(arr, cb){
if( arr.length > 0 ){
arr[0].then(function(result){
cb(result);
processInOrder(arr.slice(1), cb);
});
}
}
var deferreds = [];
for(var i=0; i<4; i++){
deferreds.push( asyncRequest(i) );
}
processInOrder(deferreds, display);
Note that while I'm not positive, I'm fairly sure that this form of recursion does not nuke the call stack for large numbers of requests.
Here it is in a jsFiddle

Any better way to combine multiple callbacks?

I need to call an async function (with loop) for multiple values and wait for those results. Right now I'm using the following code:
(function(){
var when_done = function(r){ alert("Completed. Sum of lengths is: [" + r + "]"); }; // call when ready
var datain = ['google','facebook','youtube','twitter']; // the data to be parsed
var response = {pending:0, fordone:false, data:0}; // control object, "data" holds summed response lengths
response.cb = function(){
// if there are pending requests, or the loop isn't ready yet do nothing
if(response.pending||!response.fordone) return;
// otherwise alert.
return when_done.call(null,response.data);
}
for(var i=0; i<datain; i++)(function(i){
response.pending++; // increment pending requests count
$.ajax({url:'http://www.'+datain[i]+'.com', complete:function(r){
response.data+= (r.responseText.length);
response.pending--; // decrement pending requests count
response.cb(); // call the callback
}});
}(i));
response.fordone = true; // mark the loop as done
response.cb(); // call the callback
}());
This isn't all very elegant but it does the job.
Is there any better way to do it? Perhaps a wrapper?
Async JS to the rescue (for both client-side and server-side JavaScript)! Your code may look like this (after including async.js):
var datain = ['google','facebook','youtube','twitter'];
var calls = [];
$.each(datain, function(i, el) {
calls.push( function(callback) {
$.ajax({
url : 'http://www.' + el +'.com',
error : function(e) {
callback(e);
},
success : function(r){
callback(null, r);
}
});
});
});
async.parallel(calls, function(err, result) {
/* This function will be called when all calls finish the job! */
/* err holds possible errors, while result is an array of all results */
});
By the way: async has some other really helpful functions.
By the way 2: note the use of $.each.
You can use the jQuery Deferred object for this purpose.
var def = $.when.apply(null, xhrs) with xhrs being an array containing the return values of your $.ajax() requests. Then you can register a callback def.done(function() { ... }); and use the arguments array-like object to access the responses of the various requests. to properly process them, remove your complete callback and add dataType: 'text' and use the following callback for done():
function() {
var response = Array.prototype.join.call(arguments, '');
// do something with response
}

jQuery Multiple getJSON requests

My script needs to fetch several json files on https://graph.facebook.com/xxxx, and retrieve a certain field from each json, then calculate summation.
My problem is how to print out the result after all getJSON done? With below code it will prints 0. Feel free to suggest any better approaches.
var result = 0;
$.each(urls, function (i, url) {
$.getJSON(url, function (json) {
result += json.field1;
})
});
alert(result);
Using jQuery 1.5 deferred objects:
Accumulate an array of the JQXHR objects returned by $.getJSON()
var jxhr = urls.map(function(url) {
return $.getJSON(url, function(json) {
result += json.field1;
})
});
and only $.when they're all .done():
$.when.apply($, jxhr).done(function() {
alert(result);
});
NB: this will accumulate result in the order that the AJAX calls complete, not in the order they're made.
It's not working as you are printing the result straight away, remember that the code where you concatenate the result is a callback so will fire after your alert.
On each callback you'll have to check if all have finished. replace the alert() call with your processing. :)
var result = 0;
var doneCount = 0;
$.each(urls, function (i, url) {
$.getJSON(url, function (json) {
doneCount++;
result += json.field1;
if (doneCount == urls.length) {
alert(result);
}
})
});

Categories