Collect AJAX results in order - javascript

I have an array of values (myarray), that I want to iterate through and run an AJAX request on each iteration. I've put each ajax request inside another array (requests), so that I can call an alert when all AJAX requests have completed:
Like so:
var requests = [];
for (i = 0; i < myarray.length; ++i) {
requests.push($.ajax({
url: 'anotherurl?=' + myarray[i],
dataType: "JSONP",
success: function (data) {
array_of_results.push(data);
}
}));
}
$.when.apply($, requests).done(function() {
alert('complete');
});
All the results are collected in array_of_results. However due to the AJAX requests taking different lengths of time to complete, this array doesn't have the results in the original order.
Is there any way to order this array?
I hope I've made sense. I appreciate this is quite convoluted.

Have you tried the following? I think this should work. All the responses should be available, in order, in the success function of the when().
var requests = [];
for (i = 0; i < myarray.length; ++i) {
requests.push($.ajax({
url: 'anotherurl?=' + myarray[i],
dataType: "JSONP"
}));
}
$.when.apply($, requests).done(function() {
array_of_results = arguments;
alert('complete');
});

Instead of using a loop consider using recursion. Here's a complete example:
var myArray = [
"somevalue",
"some other value",
"another value"
];
var i = myArray.length;
var responses = [];
function doRequests(){
i--;
$.ajax({
url: "myurl.php",
data: {paramname: myArray[i]}
}).done(function(response){
responses.push(response);
if(i>0) doRequests();
else{
// all requests sent.. do stuff
// responses array is in order
console.log(responses);
alert("all done!");
}
});
}

You can add a custom attribute to your $.ajax object, setted to your i var.
var requests = [];
for (i = 0; i < myarray.length; ++i) {
requests.push($.ajax({
url: 'anotherurl?=' + myarray[i],
dataType: "JSONP",
myCustomIndex: i,
success: function (data) {
array_of_results[this.myCustomIndex] = data;
}
}));
}
$.when.apply($, requests).done(function() {
alert('complete');
});
JavaScript is really permisive, if you attribute a value to an array out of its bounds (higher than 0), the size of the array will be automaticaly set to the right amount.

How about using jquery.ajax call with async setting as false. This way the reaponse will be in order as requested...

Building on #Christo's answer - using arrays map function
var array_of_results = [];
var requests = myarray.map(function(item, index) {
return $.ajax({
url: 'anotherurl?=' + item,
dataType: "JSONP",
success: function (data) {
array_of_results[index] = data;
}
}
});
...

Related

Ajax calls inside loop need sequential responses

I need to make 3 or less ajax calls, and the responses need to be appended to the dom in the same order they were requested.
I have the following function, but the problem is that the responses that I get are not necessarily in the correct order when they get appended to the dom.
I wouldn't want to use the async: false property because it blocks the UI and it's a performance hit of course.
mod.getArticles = function( ){
//mod.vars.ajaxCount could start at 0-2
for( var i = mod.vars.ajaxCount; i < 3; i++ ){
//mod.vars.pushIds is an array with the ids to be ajaxed in
var id = mod.vars.pushIds[i];
$.ajax({
url: '/ajax/article/' + id + '/',
type: "GET",
dataType: 'HTML',
error: function() {
console.error('get article ajax error');
}
}).done( function( data ) {
if (data.length) {
mod.appendArticle( data );
} else {
console.error('get article ajax output error');
}
});
}
};
You need to append the article to a certain position, based on for example the i variable you have. Or you could wait for all of the requests and then append them in order. Something like this:
mod.getArticles = function( ){
var load = function( id ) {
return $.ajax({
url: '/ajax/article/' + id + '/',
type: "GET",
dataType: 'HTML',
error: function() {
console.error('get article ajax error');
});
};
var onDone = function( data ) {
if (data.length) {
mod.appendArticle( data );
} else {
console.error('get article ajax output error');
}
};
var requests = [];
for( var i = mod.vars.ajaxCount; i < 3; i++ ){
requests.push(load(mod.vars.pushIds[i]));
}
$.when.apply(this, requests).done(function() {
var results = requests.length > 1 ? arguments : [arguments];
for( var i = 0; i < results.length; i++ ){
onDone(results[i][0]);
}
});
};
Here is an example using i to append them in the proper order when they all finish loading:
mod.getArticles = function( ){
// initialize an empty array of proper size
var articles = Array(3 - mod.vars.ajaxCount);
var completed = 0;
//mod.vars.ajaxCount could start at 0-2
for( var i = mod.vars.ajaxCount; i < 3; i++ ){
// prevent i from being 3 inside of done callback
(function (i){
//mod.vars.pushIds is an array with the ids to be ajaxed in
var id = mod.vars.pushIds[i];
$.ajax({
url: '/ajax/article/' + id + '/',
type: "GET",
dataType: 'HTML',
error: function() {
console.error('get article ajax error');
}
}).done( function( data ) {
completed++;
if (data.length) {
// store to array in proper index
articles[i - mod.vars.ajaxCount] = data;
} else {
console.error('get article ajax output error');
}
// if all are completed, push in proper order
if (completed == 3 - mod.vars.ajaxCount) {
// iterate through articles
for (var j = mod.vars.ajaxCount; j < 3; j++) {
// check if article loaded properly
if (articles[j - mod.vars.ajaxCount]) {
mod.appendArticle(articles[j - mod.vars.ajaxCount]);
}
}
}
});
}(i));
}
};
var success1 = $.ajax...
var success2 = $.ajax...
var success3 = $.ajax...
$.when(success1, success2, success3).apply(ans1, ans2, ans3) {
finalDOM = ans1[0]+ans2[0]+ans3[0];
}
Check this for more reference. This is still async, but it waits for all of them to complete. You know the order of invocation already, as its done through your code, so add the dom elements accordingly.
Solutions that rely solely on closures will work up to a point. They will consistently append the articles of a single mod.getArticles() call in the correct order. But consider a second call before the first is fully satisfied. Due to asynchronism of the process, the second call's set of articles could conceivably be appended before the first.
A better solution would guarantee that even a rapid fire sequence of mod.getArticles() calls would :
append each call's articles in the right order
append all sets of articles in the right order
One approach to this is, for each article :
synchronously append a container (a div) to the DOM and keep a reference to it
asynchronously populate the container with content when it arrives.
To achieve this, you will need to modify mod.appendArticle() to accept a second parameter - a reference to a container element.
mod.appendArticle = function(data, $container) {
...
};
For convenience, you may also choose to create a new method, mod.appendArticleContainer(), which creates a div, appends it to the DOM and returns a reference to it.
mod.appendArticleContainer = function() {
//put a container somewhere in the DOM, and return a reference to it.
return $("<div/>").appendTo("wherever");
};
Now, mod.getArticles() is still very simple :
mod.getArticles = function() {
//Here, .slice() returns a new array containing the required portion of `mod.vars.pushIds`.
//This allows `$.map()` to be used instead of a more cumbersome `for` loop.
var promises = $.map(mod.vars.pushIds.slice(mod.vars.ajaxCount, 3), function(id) {
var $container = mod.appendArticleContainer();//<<< synchronous creation of a container
return $.ajax({
url: '/ajax/article/' + id + '/',
type: "GET",
dataType: 'HTML'
}).then(function(data) {
if (data.length) {
mod.appendArticle(data, $container);//<<< asynchronous insertion of content
} else {
return $.Deferred().reject(new Error("get article ajax output error"));
}
}).then(null, function(e) {
$container.remove();//container will never be filled, so can be removed.
console.error(e);
return $.when(); // mark promise as "handled"
});
});
return $.when.apply(null, promises);
};
mod.getArticles() now returns a promise of completion to its caller, allowing further chaining if necessary.
Try utilizing items within mod.vars array as indexes; to set as id property of $.ajaxSettings , set returned data at this.id index within an array of responses. results array should be in same order as mod.vars values when all requests completed.
var mod = {
"vars": [0, 1, 2]
};
mod.getArticles = function () {
var results = [];
var ids = this.vars;
var request = function request(id) {
return $.ajax({
type: "GET",
url: "/ajax/article/" + id + "/",
// set `id` at `$.ajaxSettings` ,
// available at returned `jqxhr` object
id: id
})
.then(function (data, textStatus, jqxhr) {
// insert response `data` at `id` index within `results` array
console.log(data); // `data` returned unordered
// set `data` at `id` index within `results
results[this.id] = data;
return results[this.id]
}, function (jqxhr, textStatus, errorThrown) {
console.log("get article ajax error", errorThrown);
return jqxhr
});
};
return $.when.apply(this, $.map(ids, function (id) {
return request(id)
}))
.then(function () {
$.map(arguments, function (value, key) {
if (value.length) {
// append `value`:`data` returned by `$.ajax()`,
// in order set by `mod.vars` items:`id` item at `request`
mod.appendArticle(value);
} else {
console.error("get article ajax output error");
};
})
});
};
mod.getArticles();
jsfiddle http://jsfiddle.net/6j7vempp/2/
Instead of using a for loop. Call your function in response part of previous function.
//create a global variable
var counter = 0;
function yourFunc(){
mod.getArticles = function( ){
//mod.vars.ajaxCount could start at 0-2
//mod.vars.pushIds is an array with the ids to be ajaxed in
var id = mod.vars.pushIds[counter ];
$.ajax({
url: '/ajax/article/' + id + '/',
type: "GET",
dataType: 'HTML',
error: function() {
console.error('get article ajax error');
}
}).done( function( data ) {
if (data.length) {
mod.appendArticle( data );
} else {
console.error('get article ajax output error');
}
//increment & check your loop condition here, so that your responses will be appended in same order
counter++;
if (counter < 3)
{ yourFunc(); }
});
};
}
I'm faced same problem i'm solve this problem using following way.
just use async for get sequence wise response
<script type="text/javascript">
var ajax1 = $.ajax({
async: false,
url: 'url',
type: 'POST',
data: {'Data'},
})
.done(function(response) {
console.log(response);
});

Sending a variable with ajax call .done

Is there a possible way to send the variable, 'schedule_id[i]', with the result of the call. Also is it possible to add this variable in the data object?
My code:
for (var i = 0; i < schedule_id.length; i++) {
//Ajax call
$.ajax({
url: "http://api.viewer.zmags.com/schedules/" + schedule_id[i] + "?key=" + api_key
})
//
.done(function(data){
}
So you need asynchronies ajax call in synchronies manner right?
So you need to create one separate function which is call ajax request and return the result and use in subsequent request.
Like:-
for (var i = 0; i < schedule_id.length; i++) {
var result;
if (i == 0)
result = callfunction(0,schedule_id[i]);
else
result = callfunction(result,schedule_id[i]);
}
function callfunction(passresult,schedule_id) {
$.ajax({
url: "http://api.viewer.zmags.com/schedules/" + schedule_id + "?key=" + api_key
})
.done(function (data) {
return data;
});
}
construct the ajax call like this:
$.ajax({
url: 'http://api.viewer.zmags.com/schedules/',
type: 'POST' // or GET,
data: {
schedule_ids: schedule_id, //array
key: api_key
},
success: function (data) {
//success callback
}
});

Clean way to make Subsequent AJAX Calls to API based on Data

So I have a conceptual question regarding the cleanest way to make subsequent AJAX calls to an API based on the returned data.
A quick example:
A function, which encompasses the call would look like this:
function makeCall(headers, min, max) {
$.ajax({
headers: headers,
url: "https://coolapi.com/data?begIndex" + min + "&endIndex=" + max + "&begTimestamp=1404198000000&endTimestamp=1409554800000",
type: "GET",
dataType: 'JSON'
});
}
makeCall(headers, 0, 20);
The beg / end index (min/max), determine the amount of data I'll get back in the array. The API will only return a maximum of 20 items in the array, but it will also return me a COUNT of how many items total exist in that array. An example of the data returned is below:
{
count = 133;
result = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19];
}
So my next call would be:
makeCall(headers, 20, 40);
and so on so forth, until I got all 133 items from the array.
The question is...what is the cleanest way to continue to make subsequent calls until I've gotten and stored all 133 items from the array? Given that the count could be any number, it's hard to imagine how I can do this. I was thinking of nesting more ajax calls in a "success" function, but it's not scalable if I get back a number like 300.
Does anyone have any advice on how to proceed?
Thanks in advance!
EDIT:
So based on the advice in the comment, I've attemped to make the call recursive--but it doesn't seem to function as intended:
var theData = [];
var minCounter=0;
var maxCounter= minCounter + 20;
function makeCall(headers, min, max) {
$.ajax({
headers: headers,
url: "https://coolapi.com/data?begIndex" + min + "&endIndex=" + max + "&begTimestamp=1404198000000&endTimestamp=1409554800000",
type: "GET",
dataType: 'JSON',
success: function (data) {
theData.push(data.result);
newMin = minCounter + 20;
if (data.count >= theData.length ) {
makeCall(headers, newMin, maxCounter);
}
}
});
}
makeCall(headers, minCounter, maxCounter);
How do properly increment the variable as well as set the flag?
SECOND EDIT:
The method below works using the second comment's suggestion, but there are some issues here as well...
function doAjax(headers, min, dObject) {
var max = min + 20;
$.ajax({
headers: headers,
url: "https://coolapi.com/data?begIndex" + min + "&endIndex=" + max + "&begTimestamp=1404198000000&endTimestamp=1409554800000",
type: "GET",
dataType: 'JSON',
success: function (data) {
results.push(data);
window.count = data.count;
dObject.resolve();
}
});
}
// array that will contain all deferred objects
var deferreds = [];
// array that will contain all results
var results = [];
// make the ajax calls
for (var i = 20; i < 133 ; i+= 20) {
var dObject = new $.Deferred();
deferreds.push(dObject);
doAjax(headers, i, dObject);
}
// check if all ajax calls have finished
$.when.apply($, deferreds).done(function() {
console.log(results);
});
var dObject = new $.Deferred();
doAjax(headers,0, dObject);
First, the data doesn't push to the array in order. There doesn't seem anyway to fix this. Also strangely enough, in the for loop--I have to set the number for it to actually work. Trying to store it in a variable doesn't seem to work as well...Suggestions here?
Here's a working implementation based around the code you started with. Code is commented to help you understand what is happening:
// Change these constants to suit your purposes.
var API_URL = 'https://coolapi.com/data';
var HEADERS = {};
var API_RESULTS_PER_REQUEST = 20;
var MAX_API_CALLS = 20;
// Count API calls to trigger MAX_API_CALLS safety lock.
var apiCalls = 0;
// Function we'll call to get all our data (see bottom).
function collectApiData(begTimestamp, endTimestamp) {
var dataReady = jQuery.Deferred();
var params = {
'begTimestamp': begTimestamp,
'endTimestamp': endTimestamp
};
var datasetsCollected = requestDatasets(params);
jQuery.when(datasetsCollected).then(function(data) {
dataReady.resolve(data);
});
return dataReady;
}
// Makes individual AJAX call to API
function callApi(params, headers) {
var $request = jQuery.ajax({
url: API_URL,
headers: headers,
data: params,
type: 'GET',
dataType: 'JSON'
});
return $request;
}
// Recursive function that makes API calls until data is collected, there is an
// error, or MAX_API_CALLS limit is hit.
function requestDatasets(params, resultsReady, resultsFetched) {
resultsReady = ( resultsReady !== undefined ) ? resultsReady : jQuery.Deferred();
resultsFetched = ( resultsFetched !== undefined ) ? resultsFetched : [];
// Trigger safety to avoid API abuse
if ( apiCalls >= MAX_API_CALLS ) {
console.error('Exceeded max API calls:', MAX_API_CALLS);
resultsReady.resolve(resultsFetched);
}
// Set index data
params.begIndex = resultsFetched.length;
params.endIndex = resultsFetched.length + API_RESULTS_PER_REQUEST;
// Request dataset from API
var apiRequest = callApi(params, HEADERS);
apiCalls += 1;
// Callback once API request has completed and data is ready
jQuery.when(apiRequest).done(function(data) {
var apiResultCount = data.count;
resultsFetched = resultsFetched.concat(data.result);
console.debug('Fetched', resultsFetched.length, 'of', apiResultCount, 'API results');
if ( apiResultCount > resultsFetched.length ) {
console.debug('Making another API call');
requestDatasets(params, resultsReady, resultsFetched);
}
else {
console.debug('Results all fetched!');
resultsReady.resolve(resultsFetched);
}
});
jQuery.when(apiRequest).fail(function(data) {
console.error('API error: returning current results.');
resultsReady.resolve(resultsFetched);
});
return resultsReady;
}
// Run script
var dataReady = collectApiData('1404198000000', '1409554800000');
jQuery.when(dataReady).then(function(data) {
console.log(data);
});
Here's a working fiddle that mocks the API using httpbin.org:
http://jsfiddle.net/klenwell/mfhLxun2/

Proper way to loop jQuery ajax and pass arguments

I want to solve once for all the problem of looping ajax request and passing 'index' into it (the problem below):
for (var index = 0; index < 4; index++) {
$.ajax({
url: 'http://graph.facebook.com/',
dataType: 'jsonp',
success: function(json) {
console.log(json[index]);
}
});
}
in this code within every 'success' callback 'index' will be 3. But I want to invoke callback with 0, 1, 2, 3. Many people are placing ajax request within a closure:
for (var index = 0; index < 4; index++) {
(function(index){$.ajax({
url: 'http://graph.facebook.com/',
dataType: 'jsonp',
success: function(json) {
console.log(json[index]);
}
});
})(index);
}
what in my opinion is a huge mistake - what if the request won't be there at the time? Than 'json' variable will be 'undefined'.
Does any of You guys have some proper way to solve this issue?
Actually the JSON will not be undefined.
If you would break the following code apart it would become more clear:
So instead of this:
for (var index = 0; index < 4; index++) {
(function(index){$.ajax({
url: 'http://graph.facebook.com/',
dataType: 'jsonp',
success: function(json) {
console.log(json[index]);
}
});
})(index);
}
...you can also write it like this:
function myFunction(index) {
$.ajax({
url: 'http://graph.facebook.com/',
dataType: 'jsonp',
success: function(json) {
console.log(json[index]);
}
});
}
// and call it like this
for (var index = 0; index < 4; index++) {
myFunction(index);
}
As you might already see, how are any of those two variables going to change by another call while they are defined inside the function?
(On a side note: I think it actually looks cleaner this way)

Append Order - jQuery

I am trying to get the string to order by how it is displayed (red,blue,orange,black). For some reason, it will append the order randomly. For Example: it would output (blue,orange,red,black). Any help would be great. Thanks.
var tCookie = "red,blue,orange,black";
var Cookies = tCookie.split(',');
if (Cookies) {
for (var i = 1; i <= Cookies.length; i++) {
var dataString = "TabId="+Cookies[i]+"";
$.ajax({
type: "POST",
url: "includes/tab.php",
data: dataString,
cache: false,
success: function(html){
$("#Dynamic_Tab").append(html).children(':last').hide().fadeIn("fast");
}
});
}
}
You could have a list of requests and responses, and start appending when all are done, so that the order is always correct:
var deferreds = [],
results = [];
for (var i = 1; i <= Cookies.length; i++) {
(function(i) { // to freeze i
var dataString = "TabId="+Cookies[i]+"";
deferreds.push($.ajax({
type: "POST",
url: "includes/tab.php",
data: dataString,
cache: false,
success: function(html){
results[i] = html; // insert at the synchronous position
}
}));
})(i);
}
$.when.apply($, deferreds).then(function() {
$.each(results, function(i, html) {
$("#Dynamic_Tab").append(html).children(':last').hide().fadeIn("fast");
});
});
You can use deferred objects here to only append the HTML after all of the AJAX requests come back:
//create array to store XHR objects that will resolve when the AJAX requests return
//also create an object to store the AJAX responses
var jqXHRs = [],
responses = {};
//iterate through each of the cookie indexes
$.each(cookies, function (index, value) {
//create the dataString and cache the value of this index so it can be used in the success callback for the AJAX request associated with this index
var dataString = "TabId=" + value,
thisValue = value;
//store an empty string in the output variable for the current index, this keeps it's place in-line
responses[thisValue] = '';
//do the AJAX request and store it's XHR object in the array with the rest
jqXHRs[jqXHRs.length] = $.ajax({
type : "POST",
url : "includes/tab.php",
data : dataString,
cache : false,
success : function (html) {
//now that the AJAX request has returned successfully, add the returned HTML to the output variable for this index
responses[thisValue] = html;
}
});
});
//wait for all of the XHR objects to resolve then add all the HTML to the DOM
$.when(jqXHRs).then(function () {
//all of the AJAX requests have come back and you can now add stuff to the DOM
var $element = $("#Dynamic_Tab");
$.each(responses, function (index, value) {
$element.append(value).children(':last').hide().delay(index * 250).fadeIn(250);
}
});
The .delay() is so that each new row will fade in, in order.

Categories