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
Related
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>
I have an array that can hold an unknown amount of indexes in it. Each index is used to send data with an ajax call. I am looping through with a for loop gathering the data from the successful call and pushing it into an empty array. At the end of the unknown amount of calls I then need to use that newly gathered array in my view. newDataArray is executed at the bottom before the loops are done and therefor it is still empty. How do I finish all the calls then do what is at the bottom?
If it helps, I am doing this in React with the Flux pattern. But the same issue may be done not in React. Here is a mock sample of what I am trying to do:
JS
case 'execute-calls':
//This is the new array to push to
var newDataArray = [];
//Url to call
var url = 'http://dev.markitondemand.com/Api/v2/Quote/jsonp';
for(let i = 0; i < payload.data.length; i++){
//given array of data that needs to be sent with call
let symb = { symbol: payload.data[i]};
$.ajax({
data: symb,
url: url,
dataType: "jsonp",
})
.done(function(data){
let updatedData = {
//...data that is stored from response
};
newDataArray.push(updatedData);
})
.fail(function(error){
//console.log(error);
});
}
//This will be updating the state object which is above the switch cases
//However this is ran before the end of the loops so newDataArray is empty
var updateTicker = {
updatedTicker: true,
updatedTickerSymbols: newDataArray
};
assign(stockData,updateTicker);
getStockData.emitChange();
break;
You can make use of the fact that $.ajax() actually returns a deferred object, and use it to create an array of deferreds. e.g.
var symbols = [1, 2, 3, 4];
var deferreds = symbols.map(function (symbol) {
return $.ajax({
url: 'http://dev.markitondemand.com/MODApis/Api/v2/Quote/jsonp',
data: { symbol: symbol },
dataType: 'jsonp'
});
});
You can resolve multiple deferreds at once with $.when(). There is a complication however, $.when() expects a list of parameters rather than array. We can solve this by using Function#apply.
To add to the complication, the callback function is also called with a list of arguments. Since we don't know how many arguments there are, we'll use the arguments pseudo-array. And since arguments isn't an actual array, we'll loop through it by using Function#call on Array#prototype.
$.when.apply($, deferreds).done(function () {
Array.prototype.forEach.call(arguments, function (response) {
console.log(response[0].Message);
});
}).fail(function (jqXHR, textStatus, error) {
console.error(error);
});
[UPDATED to include fail() call]
If you're using ES6 this is much more elegant:
$.when(...deferreds).done((...responses) => {
responses.forEach((response) => {
console.log(response[0].Message);
});
});
When ever you are dealing with ajax calls and have to do some operations at the end of all async calls then better choice would be to use Callback functions.
Modifying your code to use the call back,
function AsyncLoopHandler(index) {
if (index > payload.data.length) {
// all the indexes have finished ajax calls do your next step here
var updateTicker = {
updatedTicker: true,
updatedTickerSymbols: newDataArray
};
assign(stockData, updateTicker);
getStockData.emitChange();
}
else {
//given array of data that needs to be sent with call
let symb = { symbol: payload.data[index] };
$.ajax({
data: symb,
url: url,
dataType: "jsonp",
})
.done(function (data) {
let updatedData = {
//...data that is stored from response
};
newDataArray.push(updatedData);
AsyncLoopHandler(index++); // call the function again with new index
})
.fail(function (error) {
//console.log(error);
});
}
}
Now for starting this recursive function just start it by passing the index 0.
AsyncLoopHandler(0);
So all the ajax calls will be executed one after the other as if its an synchronous requests, And the if check will see if all the indexes are complete and then run your logic. Let me know if this helps
suggest use promise, logic would like
var urls= [x,x,x,x];
var results = [];
var qs = $.map(urls,function(url){
return function(){
var deferred = Q.defer();
$.ajax({
success:function(){
results.push(url)
deferred.reslove();
},error:function(){
deferred.reslove();
}
})
return deferred;
}
})
Q.all(qs).then(function(){
console.log(results )
});
or use yield and co in new standard
https://github.com/kriskowal/q
I am relatively a newbie to jquery and ajax and am trying to use the concept of deferrals and promises to solve this problem I have.
I would like to do the following:
Call a list of URLS and process the result returned from the urls. I would like to first process the results in parallel, and then combine the processed results to give me a final result.
Th pseudo-code is as follows:
var deferredAjaxCalls = [];
for (var i = 0; i < jobsListLength; i++) {
deferredAjaxCalls.push(
$.ajax({
url:"/myurl",
method:"POST",
contentType:"application/json",
dataType:"json",
data:mydata,
success:function(result){
//Some code here that is performance intensive
}
});
}
$.when.apply(this,deferredAjaxCalls).done(function(){
for (var k=0; k< arguments.length;k++){
//combine the results of the individual results of the
// success part of all the ajax calls and execute some more
//code synchronously
}
}).fail( function (jqXHR, status, error) {
//Log failed status
});
Initially, I moved all of the code from the success part inside the $.when.apply().However, this resulted in very slow performance as there is a lot of intensive computation that is now executed synchronously. So I am looking for a way to execute part of the code independently, and the final piece synchronously
I did read about using promises, but could not find any example where promises are used with an array of ajax calls with intermediate processing before finally synchronising in the when.apply() block
What would be a good way to solve this problem?
Thanks!
Starting with an array jobsList, you probably want something like this :
var deferredAjaxCalls = jobsList.map(function(job) {
return $.ajax({
url: "/myurl",
method: "POST",
contentType: "application/json",
dataType: "json",
data: mydata
}).then(process);// where `process` is a function that accepts $.ajax's (data, textStatus, jqXHR) and returns a *single* value/object - the result of the processing. This will standardise the data delivered below by $.when() to its success handler.
});
$.when.apply(null, deferredAjaxCalls).then(function() {
// Due to `.then(process)` above, `arguments` are guaranteed to comprise one arg per ajax call.
// Otherwise you potentially have the problem reported here - http://stackoverflow.com/questions/12050160/
for (var k=0; k<arguments.length; k++) {
// Combine the results of the individual results of the success part of all the ajax calls and execute some more code synchronously.
}
// In this function deliver an error by returning `$.Deferred().reject(new Error('myReason'))`
return combined_result;
}, function(jqXHR, status, error) {
// This hander will receive multiple $.ajax() params, which are best normalised into a single Error object.
return new Error(status); // similar to .then(process) above, reduce $.ajax's error args to a single "reason".
}).then(null, function(err) {
// All errors delivered by code above arrive here as a js Error.
// But please note that, in jQuery <v3.0, any uncaught errors above will genuinely throw (to the console).
console.log(err.message);
});
You can try using deferreds:
var req_responses = [];
var deferreds = [];
for(var i in jobs) {
deferreds[i] = new $.Deferred();
}
for(var i in jobs) {
(function(i) {
$.ajax ({
url: ".",
type: "POST",
dataType: "json",
done: function(response) {
//process the response
req_responses[i] = response;
deferreds[i].resolve();
}
});
})(i);
}
$.when.apply(deferreds).then(function(os) {
//all the responses are in req_responses
//finish processing
alert("done");
});
I'm using jQuery to send an AJAX request, retrieving data from a server.
That data is then appended to an element. This should happen 5 times, but it will always happen randomly either 3, 4, or 5 times. Basically, sometimes the loop will skip the AJAX request, but the majority of the time it catches it. How do I make sure it completes the request five times every time? and what is the reason behind this random behavior of skipping AJAX request?(side note. I've checked the request errors, but it never alerted of a request failure)
Here's my JS:
while (counter < 6) {
$.ajax({
url:'http://whisperingforest.org/js/getQuote.php',
async: false,
dataType: 'jsonp',
success:function(data){
$('.quoteList').append('<li>' + data +'</li>');
totalQuotes++;
}
});
counter++;
}
P.s. this happens on a button press.
Don't do it synchronously. Use the callback. Here is a demo for you: http://jsfiddle.net/y45Lfupw/4/
<ul class="quoteList"></ul>
<input type="button" onclick="getData();" value="Go Get It!">
<script>
var counter = 0;
window.getData=function()
{
/* This IF block has nothing to do with the OP. It just resets everything so the demo can be ran more than once. */
if (counter===5) {
$('.quoteList').empty();
counter = 0;
}
$.ajax({
/* The whisperingforest.org URL is not longer valid, I found a new one that is similar... */
url:'http://quotes.stormconsultancy.co.uk/random.json',
async: true,
dataType: 'jsonp',
success:function(data){
$('.quoteList').append('<li>' + data.quote +'</li>');
counter++;
if (counter < 5) getData();
}
});
}
</script>
Setting async to false blocks the main thread (responsible for
executing JavaScript, rendering the screen, etc) and waits for the XHR
to complete.
This is almost always a terrible idea. Users don't like unresponsive
UIs. (https://stackoverflow.com/a/20209180/3112803)
Just search stackoverflow for ajax async: false and you will find MANY good explanations on this. Everyone will discourage you from using async:false. Here's is a great explanation: https://stackoverflow.com/a/14220323/3112803
Very interesting methods provided by jQuery when you are executing loops of asyncroniouse request and detect all ajax request completed or not. It is possible by using
var users=["a","b","c","d","e","f","g","h"];
var async_request=[];
var responses=[];
for(i in users)
{
// you can push any aysnc method handler
async_request.push($.ajax({
url:'', // your url
method:'post', // method GET or POST
data:{user_name: users[i]},
success: function(data){
console.log('success of ajax response')
responses.push(data);
}
}));
}
$.when.apply(null, async_request).done( function(){
// all done
console.log('all request completed')
console.log(responses);
});
Here $.when provides a way to execute callback functions based on zero
or more objects, usually Deferred objects that represent asynchronous
events.
apply() converts array elements as different arguments in
function
$.done is call function after all async. request are
completed.
You can use ES6 async/await Promises like this
function fromServer(){
return new Promise((resolve, reject) => {
$.ajax({
url:'http://whisperingforest.org/js/getQuote.php',
async: false,
dataType: 'jsonp',
success:function(data){
resolve(data)
}
});
})
}
var totalQuotes = 0;
async function domManipulation(){
while (counter < 6) {
var data = await fromServer();
$('.quoteList').append('<li>' + data +'</li>');
totalQuotes++;
}
}
domManipulation()
JSFIDDLE
Grateful for any insight into what I'm misunderstanding here. My requirement is as follows:
I have an array of URLs. I want to fire off an AJAX request for each URL simultaneously, and as soon as the first request completes, call the first callback. Then, if and when the second request completes, call that callback, and so on.
Option 1:
for (var i = 0; i < myUrlArray.length; i++) {
$.ajax({
url: myUrlArray[i]
}).done(function(response) {
// Do something with response
});
}
Obviously this doesn't work, as there is no guarantee the responses will complete in the correct order.
Option 2:
var promises = [];
for (var i = 0; i < myUrlArray.length; i++) {
promises.push($.ajax({
url: myUrlArray[i]
}));
}
$.when.apply($, promises).then(function() {
// Do something with each response
});
This should work, but the downside is that it waits until all AJAX requests have completed, before firing any of the callbacks.
Ideally, I should be able to call the first callback as soon as it's complete, then chain the second callback to execute whenever that response is received (or immediately if it's already resolved), then the third, and so on.
The array length is completely variable and could contain any number of requests at any given time, so just hard coding the callback chain isn't an option.
My attempt:
var promises = [];
for (var i = 0; i < myUrlArray.length; i++) {
promises.push($.ajax({
url: myUrlArray[i] // Add each AJAX Deferred to the promises array
}));
}
(function handleAJAX() {
var promise;
if (promises.length) {
promise = promises.shift(); // Grab the first one in the stack
promise.then(function(response) { // Set up 'done' callback
// Do something with response
if (promises.length) {
handleAJAX(); // Move onto the next one
}
});
}
}());
The problem is that the callbacks execute in a completely random order! For example, if I add 'home.html', 'page2.html', 'page3.html' to the array, the order of responses won't necessarily be 'home.html', 'page2.html', 'page3.html'.
I'm obviously fundamentally misunderstanding something about the way promises work. Any help gratefully appreciated!
Cheers
EDIT
OK, now I'm even more confused. I made this JSFiddle with one array using Alnitak's answer and another using JoeFletch's answer and neither of them work as I would expect! Can anyone see what is going on here?
EDIT 2
Got it working! Based on JoeFletch's answer below, I adapted the solution as follows:
var i, responseArr = [];
for (i = 0; i < myUrlArray.length; i++) {
responseArr.push('0'); // <-- Add 'unprocessed' flag for each pending request
(function(ii) {
$.ajax({
url: myUrlArray[ii]
}).done(function(response) {
responseArr[ii] = response; // <-- Store response in array
}).fail(function(xhr, status, error) {
responseArr[ii] = 'ERROR';
}).always(function(response) {
for (var iii = 0; iii < responseArr.length; iii++) { // <-- Loop through entire response array from the beginning
if (responseArr[iii] === '0') {
return; // As soon as we hit an 'unprocessed' request, exit loop
}
else if (responseArr[iii] !== 'done') {
$('#target').append(responseArr[iii]); // <-- Do actual callback DOM append stuff
responseArr[iii] = 'done'; // <-- Set 'complete' flag for this request
}
}
});
}(i)); // <-- pass current value of i into closure to encapsulate
}
TL;DR: I don't understand jQuery promises, got it working without them. :)
Don't forget that you don't need to register the callbacks straight away.
I think this would work, the main difference with your code being that I've used .done rather than .then and refactored a few lines.
var promises = myUrlArray.map(function(url) {
return $.ajax({url: url});
});
(function serialize() {
var def = promises.shift();
if (def) {
def.done(function() {
callback.apply(null, arguments);
serialize();
});
}
})();
Here's my attempt at solving this. I updated my answer to include error handling for a failed .ajax call. I also moved some code to the complete method of the .ajax call.
var urlArr = ["url1", "url2"];
var responseArr = [];
for(var i = 0; i < length; i++) {
responseArr.push("0");//0 meaning unprocessed to the DOM
}
$.each(urlArr, function(i, url){
$.ajax({
url: url,
success: function(data){
responseArr[i] = data;
},
error: function (xhr, status, error) {
responseArr[i] = "Failed Response";//enter whatever you want to place here to notify the end user
},
complete: function() {
$.each(responseArr, function(i, element){
if (responseArr[i] == "0") {
return;
}
else if (responseArr[i] != "done")
{
//do something with the response
responseArr[i] = "done";
}
});
}
});
})
Asynchronous requests aren't guaranteed to finish in the same order that they are sent. some may take longer than others depending on server load and the amount of data being transferred.
The only options are either to wait until they are all done, only send one at a time, or just deal with them being called possibly out of order.