I'm trying to get a better understanding of how callbacks work.
In this example, I want to get two or more XML files using AJAX, then extract the content I need from them, and then store that data in an array outside of the AJAX call. I want to use the "dataExt" array to plot a google chart, but I am getting hung up implementing callbacks properly. I guess my brain just isn't big enough yet!
Here's my code snippet.
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script>
// List of xml files.
var xmlFeed = ['rss-feed1.xml', "rss-feed2.xml"];
// An array to store each "dataString" from each XML file.
var dataExt = [];
for(var i = 0; i < xmlFeed.length; i++) {
$.ajax({
type: "GET",
dataType: "xml",
async: true,
url: xmlFeed[i],
contentType: "text/xml; charset=UTF-8",
success: function(xml){
var content = $(xml).find("content");
var dataString = (content.text());
console.log(dataString);
// Need to push "dataString" to "dataExt" array.
// dataExt = dataExt.push(dataString); <-- this doesn't work
}
}) // close ajax
} // close loop
console.log(dataExt[0]);
console.log(dataExt[1]);
</script>
When you make an AJAX request, the call to $.ajax() returns immediately. It does not wait around for the content to come back over the network. In your example, the console.log() statements are being called before the callbacks have been completed.
What you want is a single callback that gets executed once all the data you need from your various requests has been fetched. To do this you need some sort of synchronization between the various AJAX calls that you're doing. jQuery alone doesn't support this that well.
You can roll this yourself. However, this is a common enough design problem that whole libraries have been written to handle it.
Do some Googling on the Promise pattern. Once you've done that, have a look at the Q library, which is one of several implementations of the pattern. They have done most of the hard work of synchronizing multiple AJAX requests for you.
Example:
function xmlPromise(name) {
return Q.promise(function (resolve, reject, notify) {
$.ajax({
type: "GET",
dataType: "xml",
async: true,
url: name,
contentType: "text/xml; charset=UTF-8"
})
.done(function (data) {
resolve(data);
}).fail(function () {
reject();
});
});
};
var promises = [ xmlPromise('1.xml'), xmlPromise('2.xml') ];
var results = [];
Q.allSettled(promises).then(function(responses) {
console.log(responses[0].value);
console.log(responses[1].value);
results.push(responses[0].value);
results.push(responses[1].value);
});
In this example, the xmlPromise() function creates a promise object for you based on the URL you want to fetch. This promise represents a unit of work that will be completed at some time in the future. When the AJAX call you create in the promise returns successfully, it calls the resolve() method which lets Q know that the promise has been fulfilled, and the data is ready for use.
Once the promises are constructed, we pass an array of them into Q.allSettled(), which actually fires off the requests. Because these requests are asynchronous, they are executed in parallel. Once all of the promises either resolved or been rejected, Q will call the function you pass into the then() method, and pass in the results of your AJAX calls.
in original example console logs are fired before request finishes.
hope this example is what you mean:
function getXml(file, successCallback){
$.ajax({
type: "GET",
dataType: "xml",
async: true,
url: file,
contentType: "text/xml; charset=UTF-8",
success: successCallback
}) // close ajax
}
function sc(xml){
var content = $(xml).find("content");
var dataString = (content.text());
console.log(dataString);
// Need to push "dataString" to "dataExt" array.
// dataExt = dataExt.push(dataString); <-- this doesn't work
// do whatever else you want next
}
pages ['1.xml', '2.xml'].forEach(function(v, i){
getXml(v, sc);
})
Related
I have 4 tabs on my page:
MY Queue | Today | Followup | Upcoming
on page load i fire 4 ajax calls to get data from controller for all these tabs and once i get data i create list for each of the tabs.
But as ajax is asynchronous i get anomalies in my data, are there any better ways to achieve this.
i have 4 ajax calls similar to below call:
$.ajax({
url: '/opd_clinical_workflow/get_appointment_lists',
dataType: 'json',
data: {
current_date: current_date,
department_id: current_department,
doctor: current_doctor,
status: current_status,
source: "list",
q: $( "#search_appointment").val(),
},
success: function(res){
console.log(tab,res)
_this.updateMyQueueSummary(res,id,tab);
},
error: function(err){
console.log(err);
}
});
updateMyQueueSummary:
Puts data in respective tabs
createSummaryAppointment:
Creates html for me and is called in updatesummary
Chaining AJAX is your best option:
$.ajax({
url: "somewhere/1"
})
.always(function(reponse1) {
//Do something with the response
document.write("<p>1</p>");
//Then call next part
$.ajax({
url: "somewhere/2"
})
.always(function(reponse2) {
//Do something with the response
document.write("<p>2</p>");
//Then call next part
$.ajax({
url: "somewhere/3"
})
.always(function(reponse3) {
//Do something with the response
document.write("<p>3</p>");
//Then call next part
$.ajax({
url: "somewhere/4"
})
.always(function(reponse4) {
//Do something with the response
document.write("<p>4</p>");
//Now finalize it
document.write("<p>Final</p>");
})
})
})
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Notice i am using always instead of done because i don't have anywhere to actually get data from.
All the jQuery ajax methods return promise objects. This are kind of like an IOU note from a friend. You don't have the data now but it will be there at some point.
They are an extremely powerful tool if you want to avoid what is called "callback soup" where you nest ajax callbacks until the code is both unreadable and brittle.
var promise = $.ajax('/foo');
promise.pipe(function(data){
return $.ajax('/bar?foo=' + data['foo']);
});
promise.done(function(data){
// ajax call to '/bar?foo=' is complete
});
You can use jQuery.when to combine two promises. This is really useful when you want to fire two asynchronous ajax calls at once but you need the results of both to continue.
var calls = $.when( $.ajax('/foo'), $.ajax('/bar') );
calls.done(function(response_a, response_b){
// both ajax calls are complete.
});
I am using jquery ajax api to call a web method, and on success i am implementing a functionality like showHideDomElement.
function showHideDomElement(data)
{
if(data == 1 )
$('#selector').show();
else
$('#selector').hide();
}
and this is how i call it
function Validatebatch() {
$.ajax({
type: "POST",
url: getControllerURL("/Invoice") + "/ValidateBatch",
data: {
"Id": $('#someselector').val()
},
async: true, // i tried with 'false' but it affect performance
dataType: "json",
success: function(data) {
showHideDomElement(data);
}
});
}
The ajax request to Validatebatch function raised multiple times, so there are multiple active http request pointing the url completing at different time.
Due to Asynchronous behavior, the success callback can execute at different orders.
The order of finishing up is creating the problem. Please suggest a way that can bound the ajax request to behave/complete in the order it is executed (Please suggest other than jquery async property)
Thanks
First of, you have to rewrite your Validatebatch function so it return the Promise generated by the $.ajax call, e.g.:
function Validatebatch() {
return $.ajax({
then you must store the promise of each Validatebatch call and use it to trigger the next Validatebatch only after the previous has completed, you should have something like:
promise = Validatebatch(); // call Validatebatch the first time and store the returned Promise object
promise = promise.then(Validatebatch); // another call to Validatebatch, only triggered after the previous has ended
promise = promise.then(Validatebatch); // again, this third Validatebatch call will only happen after the second one has ended.
I want to make some wine. And my function does:
function wine(){
growGrapes();
process(grapes);
makeWine();
bottle();
}
However, Since my functions often consist of $.ajax() request, some other functions get carried out first. I have used the success tool, but it helps for one ajax request only.
success:function(result){
//Some Code
}
What I actually want is a sequence.
Literally, grapes get processed before growing them. What is a easiest approach?
jQuery Deferred Objects & Promises are the way to go. http://api.jquery.com/category/deferred-object/
They supports running multiple tasks in parallel or series using $.when(PassArrayOfPromisesToRunInParallel) to run processes in parallel and promise.then() to run items sequentially.
Call the next function in the success handler of the $.ajax call of the previous function!
Example:
function growGrapes(){
// lines of code
$.ajax({
success: function(result){
// call next function here - process(grapes); and so on...
}
});
}
The above makes sure the functions get called sequentially after the other..
You can make your Ajax calls synchronous (in sequence) by ensuring you have async: false in your $.ajax() settings.
For example:
$.ajax({ url: 'url',
async: false,
dataType: 'json',
success: function(data) {
}
});
First solution :
Make your ajax call syncronous by setting async : false when setting up your ajax call
$.ajax
({
async : false,
/* other settings */
});
Warning: This solution causes the UI to hand on intensive processing. This should never be used when doing anything rigorous on the server. My recommendation for using this is to only use it in checking flags or loading simple data.
Second solution :
As stated in the comments, use jQuery promises to set up the ordering. Here is a tutorial
I'll try to come back and provide a code example for this solution soon
Third solution :
Make your next call the success handler, or call the next step from the success handler
$.ajax
({
success : NextStep,
/* other settings */
})
One solution is to use queue() function. This way you can execute as many functions as you want
var ajaxQueue = $({});
$.ajaxQueue = function(ajaxOpts) {
// queue the method. a second call wont execute until this dequeues
ajaxQueue.queue(function(next) {
// for this example I serialize params, but you can save them in several variables
// and concat into ajaxOpts.data
var params = method_that_get_params_and_serialize_them();
ajaxOpts.data = params;
ajaxOpts.complete = function() {
next();
};
$.ajax(ajaxOpts);
});
};
then your functions should be like this:
function growGrapes(){
$.ajaxQueue({
cache: false,
type: "POST",
url: "someUrl",
dataType: "json",
data: "", // we fill data inside ajaxQueue() method
success: function( response) {
//do things with response
}
});
}
If you want to keep it tidy and clean to let people see how your calls are made, you can simply pass a callback function to another like this:
function growGrapes(callback) {
$.ajax({
...
success: function (){
// Something
if (typeof callback === typeof Function) callback();
},
...
});
}
function wine(){
growGrapes(function (){
process(grapes);
});
}
I am wondering is these is any way to access the results of a jquery ajax call in the form a traditional var set to function fashion. For example consider:
function getPoints(){
//An array of JSON objects
var Points;
$.ajax({
url: "js/retrievePointsDataJson.php",
dataType:'json',
type: 'POST',
}).done(function(data){
//console.log(data);
Points.append(data);
});
console.log(Points);
return Points;
}
The commented out console.log show the array of json objects whereas the outer one does not. Now, i have tries this:
var Points = $.ajax({ ...});
And i see the response text within a larger object, but am unsure how to access the responseText. console.log(Points.responseText) yields an undefined variable.
Is this possible with this approach? Here is another question that received a check mark with a similar issue.
I have another solutions, which is the encapsulate my code within the done() function and i will have access to all my data. I was just curious if what i am attempting to do is even doable.
Thank you.
yes it is possible, however, you must wait for the request to be complete before doing so. However, since you can't effectively force the return to wait until the data exists, you're only options are to return a deferred object instead, or re-write the function in such a way that allows it to accept a callback.
function getPoints(){
return $.ajax({
url: "js/retrievePointsDataJson.php",
dataType:'json',
type: 'POST'
});
}
getPoints().done(function(data){
console.log(data);
});
or
function getPoints(callback){
return $.ajax({
url: "js/retrievePointsDataJson.php",
dataType:'json',
type: 'POST',
success: callback
});
}
getPoints(function(data){
console.log(data);
});
Because the Ajax call is done asynchronously you shouldn't return it from the outside function. This would require that you somehow block until the asynchronous call completes. Instead you could pass in a callback function to the getPoints function that will handle the logic of using the points.
function getPoints(callback){
$.ajax({
url: "js/retrievePointsDataJson.php",
dataType:'json',
type: 'POST',
}).done(function(data){
callback(data);
});
}
The asynchronous nature of ajax can make things harder if your used to imperative programming, but it will make your user interface much more responsive.
The log you're calling in the outer function is working with an undefined variable because the function is asynchronous. You can't return it from getPoints because it won't have finished. Any work with the Points variable needs to happen inside the callback (the function passed to done).
I'm using the select2 jQuery based replacement for combo boxes, and I have to define a callback to process the data I receive from a json rest web service.
The problem is that, in the same callback, I have to issue another GET request to get the total numbers of matching records, so that select2 can decide if it has to load more results (it has an infinite scroll feature)
The code is something like this:
$("#country").select2({
ajax: { // instead of writing the function to execute the request we use Select2's convenient helper
url: 'http://localhost:9000/api/countries',
dataType: 'json',
data: function(term, page) {
return {
filter: term,
page: page,
len: 10
};
},
results: function(data, page) {
return {
results: data, more: ????
};
}
}
});
The problem is I don't know how to issue an async request (I'm issuing a cross-domain request, and the docs says async is not supported in that case) and wait for it to finish before returning form the results callback.
The example from select2 page is like this:
results: function (data, page) {
var more = (page * 10) < data.total; // whether or not there are more results available
// notice we return the value of more so Select2 knows if more results can be loaded
return {results: data.movies, more: more};
}
The problem is that my web service returns the total number of records from a different endpoint, so I have to make another request, like this: http: //localhost:9000/api/countries?filter=term
any idea?
You can't wait for an async callback in javascript. You have to restructure your code to do all future work based on the async response from the actual callback.
If you need to make multiple consecutive ajax calls, then you issue the first one and in the success handler or response handler for the first ajax call, you issue the second ajax call and in the response handler for the second one, you carry out whatever you want to do with the data.
If see that you're using the .select2() framework. In the framework, the results callback is where the ajax call returns. It would be in that function that you would issue the second ajax call using normal jQuery ajax calls and in the success handler from that second ajax call, you would carry out whatever you're trying to do with the eventual data you got back. You won't be able to use the normal return value of the results callback because you won't have your final data yet at the point you need to return. I think this is just a limitation of .select2() in that it only supports a single ajax call. It just means you can't use a little bit of the built-in behavior and have to apply the result yourself with your own code, but it doesn't mean you have to throw out .select2() for everything else you were using it for.
It looks like you might want to just hook the change event directly and not use their built-in ajax stuff since it doesn't look like it really provides you with much if you need two serialized ajax calls.
I studied the source code on select2, and finnally came out with this solution
var ajax = {
url: 'http://localhost:9000/api/countries',
len: 3,
};
$("#country").select2({
query: function(options) {
var data = {
filter: options.term,
page: options.page,
len: ajax.len
};
$.ajax({
url: ajax.url,
data: data,
dataType: 'json',
type: 'GET',
success: function(data) {
$.ajax({
url: ajax.url + '/count',
data: { filter: options.term },
dataype: 'json',
success: function(resp) {
var total = parseInt(resp, 10);
var more = (options.page * ajax.len) < total;
options.callback({results: data, more: more});
}
});
}
});
},
});
As you can see, when te first fetch (ajax.url) completes I issue another request (ajax.url + '/count') and only when this second request completes I call options.callback, efectively serializing both ajax calls...
In fact the ajax function from select2 has more functionality, such as throttling and dropping out-of-order responses, I just ported them too, but I left them out of this response in order not to complicate the example...
In addition to jfriend00's answer (which is excellent, BTW) I found the followgin workaround, which is basically to issue the request synchronously, which in spite jquery docs it seemd to work (at least with chromium 18.0 and jquery 1.8.0)
I'm just posting it in case anybody find it useful...
var config = {
url: 'http://localhost:9000/api/countries',
len: 20,
term: ''
}
$("#country").select2({
ajax: {
url: config.url,
dataType: 'json',
data: function(term, page) {
config.term = term;
return {
filter: term,
page: page,
len: config.len
};
},
results: function(data, page) { // parse the results into the format expected by Select2.
var more = false;
$.ajax({
url: config.url + '/count',
data: { filter: config.term },
cache: false,
async: false,
success: function(resp) {
var total = parseInt(resp, 10);
more = (page * config.len) < total;
},
async:false
});
return {
results: data, more: more
};
}
}
});