(Why) does this ajax queue overwrite response data? - javascript

I use the ajax-request queue as posted here: Wait until all jQuery Ajax requests are done?
Now i wrote the following code to implement it:
for (var i=0; i<3; i++) {
$.ajaxQueue({
url: '/action/',
data: {action:'previous',item:i*-1},
type: 'GET',
success: function(data) {
$('.item-history .itemlist').prepend(data['item_add']);
$('.item-history .itemlist:first .index').html(data['newitemindex']);
//alert(data['newitemindex'])
};
});
As long as i use the alert to proof the response from the server, everything works fine. But as soon as i run the code, as shown, without the alert, data['newitemindex'] behaves as it was a global variable - it always returns the value of the last item.
I tried to set up a jsfiddle on this, but as i have never used that site, i could not get the ajax to work. If somebody wants to have a look at it anyway: http://jsfiddle.net/marue/UfH5M/26/

Your code is setting up three ajax calls, and then applying the result of each of them to the same elements (there's no difference in the selectors you use inside your success function). For the $('.item-history .itemlist') elements, you should see the result of each call prepended to the elements because you're using prepend(), but for the $('.item-history .itemlist:first .index') elements, you're using html() which replaces the elements' contents, and so for those you'll see the result of the last call that completes.
Off-topic: To fix that, you're probably going to want to use your loop variable in some way in the success function. That could lead you to a common mistake, so here's an example of the mistake and how to avoid it.
Let's say I have these divs:
<div id='div1'></div>
<div id='div2'></div>
<div id='div3'></div>
And I want to use three ajax calls to populate them when I click a button, using a loop counter from 1 to 3. I might think I could do it like this:
$('#btnGo').click(function() {
var i;
for (i = 1; i <= 3; ++i) {
$.ajax({
url: "/agiba4/" + i,
dataType: "text",
success: function(data) {
// THIS NEXT LINE IS WRONG
$('#div' + i).html(data);
},
error: function(xhr, status, err) {
$("<p/>").html(
"Error, status = " + status + ", err = " + err
).appendTo(document.body);
}
});
}
});
Live example (which fails)
But (as indicated) that doesn't work. It doesn't work because each success function we create has an enduring reference to the i variable, not a copy of its value as of when the success function was created. And so all three success functions see the same value for i, which is the value when the function is run — long after the loop is complete. And so (in this example), they all see the value 4 (the value of i after the loop finishes). This is how closures work (see: Closures are not complicated).
To fix this, you set it up so the success function closes over something that isn't going to be updated by the loop. The easiest way is to pass the loop counter into another function as an argument, and then have the success function close over that argument instead, since the argument is a copy of the loop counter and won't be updated:
$('#btnGo').click(function() {
var i;
for (i = 1; i <= 3; ++i) {
doRequest(i);
}
function doRequest(index) {
$.ajax({
url: "/agiba4/" + index,
dataType: "text",
success: function(data) {
$('#div' + index).html(data);
},
error: function(xhr, status, err) {
$("<p/>").html(
"Error, status = " + status + ", err = " + err
).appendTo(document.body);
}
});
}
});
Live example

Related

While looping a jquery ajax call

I want to control the number of ajax calls to a controller using a while loop.
var counter = 0;
$('#filter-form').submit(function (event) {
event.preventDefault();
alert("counter init = " + counter)
while (counter < 10) {
(function () {
$.ajax({
url: '/algorithm',
method: 'GET',
data: $('#filter-form').serialize() + "&counter=" + counter,
success: function (data) {
alert("The data is " + data);
setCounter(parseInt(data))
},
error: function (xhr, status, error) {
var err = eval("(" + xhr.responseText + ")");
alert(err.Message);
}
});
})();
}
alert("counter end = " + counter)
});
function setCounter(data) {
counter = data
}
Controller:
#RequestMapping(value = "/algorithm")
#ResponseBody
public String test(#RequestParam Map<String, String> allRequestParam) {
int counter = Integer.parseInt(allRequestParam.get("counter"));
counter++;
return Integer.toString(counter);
}
The controller basically just increments the counter and returns it and in the ajax success: it will set the global counter to that number.
When I do this, the page just freezes and I cannot click anything. I put the ajax call in a function for scoping but it still does not work. When I use a for loop, it seems the ajax does not invoke because I do not get any success or error alerts.
It doesn't work for a simple reason: the $.ajax call is asynchronous.
Take this example:
$(function() {
var t = 1;
console.log("Hey, the ajax will start! t's value: " + t);
$.ajax({
url: 'www.google.com.br',
method: 'GET',
success: function (data) {
t++;
console.log("We've received an answer! t's (incremented) value: " + t);
},
error: function (xhr, status, error) {
t++;
console.log("We've received an error! t's (incremented) value: " + t);
}
});
console.log("Hey, the ajax just ended.... Not really. t's value: " + t);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
The output is:
Hey, the ajax will start! t's value: 1
Hey, the ajax just ended.... Not really. t's value: 1
We've received an error! t's (incremented) value: 2
That's because the $.ajax call is nonblocking, thus is doesn't block the program until it is finished, allowing the program to keep on executing the next line of code and continue running the ajax task in the background.
It is a recurrent issue in SO, so instead of providing solutions again here I'll ask you to read more on the questions:
How do I return the response from an asynchronous call?
How can I get jQuery to perform a synchronous, rather than asynchronous, Ajax request?
What does Asynchronous means in Ajax?
while will block synchronously until its condition is reached. Even if responses come back, the response will be asynchronous; the current thread (the while loop) will keep blocking forever.
Don't block. I don't see any reason to use a loop in the first place - instead, simply test to see if the counter is greater than the allowed number, and if it is, return:
$('#filter-form').submit(function (event) {
event.preventDefault();
alert("counter init = " + counter)
if (counter >= 10) return;
If you wanted to make multiple requests in parallel on form submit, you could do that, but you would have to keep track of the counter client-side:
var counter = 0;
$('#filter-form').submit(function (event) {
event.preventDefault();
alert("counter init = " + counter)
while (counter < 10) {
counter++;
// ... make request
As others have said your problem is that the call is asynchronous. This simple example may give you some idea about how to control the flow. It should be simple enough to apply it to your case.
I am simulating what you need to make your code work. For the errors, I am passing back null but you should bubble up any errors that may occur and either halt execution or deal with them some other way.
var count = 0; // used to store your count
// This represents the function you are
// waiting on with your ajax calls
function waitOne(num, callback) {
setTimeout(() => {
callback(null, num);
}, 1000);
}
// This represents your ajax call
function callWaitOne(callback) {
waitOne(count, (err, num) => {
// Your result is here
console.log(num);
// Callback to let the control function
// know the ajax has returned
callback(null);
});
}
// This will control the calls
function printWaitOne() {
callWaitOne((err) => {
if (count < 10) {
count++;
// Only calls if its callback
// has been called.
printWaitOne();
}
});
}
printWaitOne();

jQuery When Done on dynamically pulled function call

I have the following code:
In site-code.js
....
var ajaxContentFunc = $(origin).data("modal-content-handler");
$.when(window[ajaxContentFunc]()).done(function (resp) {
kModal.showContent(resp);
});
In another file I have the following tag and function
Click Me
....
function ajaxContentGeneration() {
var aProm = $.ajax({
url: "tests/ajax/AjaxTest.aspx",
data: { exampleType: "modal-ajax" },
dataType: "html"
});
aProm.done(function (data) {
console.log("Ajax Loaded!");
var content = $(data).find("#ajax-content");
return aProm;
});
}
I need to populate the result of the ajaxContentGeneration (whatever method that might be) into the variable to send to showContent or in other words:
1) Pull the ajaxContentFunction Name from the tag's modal-content-handler data attribute
2) Call function (in this case ajaxContentGeneration)
3) Wait for the function's ajax to complete and return the data generated (in this case html)
4) When completed pass that value to kModal.showContent(----Here----);
However currently I am getting:
1) Pulls ajaxContentFunctionName correctly
2) Calls Function (ajaxContentGeneration() function)
3) Calls kModal.showContent(undefined). This is called prematurely because the deferred isn't correctly waiting for the function call to complete (after the ajax is done).
4) Ajax Completes
Where am I messing up here ?
As far as I can tell, you are 95% there.
Use .then() instead of .done() and return the promise returned by $.ajax().then() :
function ajaxContentGeneration() {
return $.ajax({
url: "tests/ajax/AjaxTest.aspx",
data: { exampleType: "modal-ajax" },
dataType: "html"
}).then(function (data) {
return $(data).find("#ajax-content"); // this will return jQuery
// return $(data).find("#ajax-content").html(); // this will return html
});
}
You can probably also purge $.when() from the top-level call :
var ajaxContentFunc = $(origin).data("modal-content-handler");
window[ajaxContentFunc]().then(function (resp) {
// `resp` is whatever was returned by the `return $(data).find()...` statement above
kModal.showContent(resp);
});
The reason I say "probably" is that $.when() would be necessary if value-returning (not promise-returning) functions could be called instead of ajaxContentGeneration().
Another way would be to do:
// should really be renamed...
function ajaxContentGeneration(){
return $.ajax({
url : "tests/ajax/AjaxTest.aspx",
data : { exampleType: "modal-ajax" },
dataType : "html"
})
}
Somewhere else:
var ajaxContentFunc = $(origin).data("modal-content-handler");
window[ajaxContentFunc]()
.done(function(RES){
kModal.showContent( $(RES).find("#ajax-content") );
});
So the functionality of the ajaxContentGeneration function will be to return an AJAX promise, and not have it manipulated inside it, but do the manipulation where needed (getting the #ajax-content element from the response)
Note that this whole thing is bad practice JS design, and you should avoid having functions on top of the window object, but instead on another object.

in jQuery is there a ways to loop over an array such that before it loops to the next loop it first waits for the responce from the server?

Let me explain my situation,
I have a list of checkboxes in a fieldset, for each checkbox i would like to send a jquery get and wait for the response from the server which can be random time 10 seconds to a long time once i get the result display the result and continue to the next loop.
$(function() {
$("button[name=distributeGroupProgramsToCustomersNow]").click(function() {
event.preventDefault();
$("button[name=distributeGroupProgramsToCustomersNow]").attr("disabled", "");
$("input[name='distributeGroups-GroupsList']:checked").each(function ()
{
// the loop that waits for the response
});
$("button[name=distributeGroupProgramsToCustomersNow]").removeAttr("disabled", "");
});
});
How do i achieve this in jQuery?
Any help is greatly appriciated, Thanks!
You can do this by making the call to your ajax synchronous, that way the loop has to wait for the response before it can continue.
$.each(arrayOfItems, function(i,v){
$.ajax({
url: 'path/to/my/file.extension',
type: 'GET', //this is already the default, only placed as an example.
async: false, // this is what you want to make sure things 'wait'.
data: 'checkedValue='+v, //value of our item.
success: function(data){
//manipulate / insert the data where you need.
$("#someElement").append(data);
}
});
});
How it works
For each item, we have an ajax call. Disabling 'asynchronous' ajax forces the server to 'wait' on the previous AJAX request to be completed before processing the next AJAX request. This replicates the behavior that you want.
you can write a function that calls itself
var ajaxLoopInput = $("input[name='distributeGroups-GroupsList']:checked");
var i = 0;
ajaxIt(i);
function ajaxIt(index){
if(i < ajaxLoopInput.length){
ajaxLoopInput.eq(index).dosomething(); //if you need the current item in list.
$.ajax({
//your ajax stuff
success : function(){
i++;
ajaxIt(i);
}
});
}else{
return false;
}
}
The best way to handle this without blocking and using synchronous calls, is to use the array as a queue, and have a function that pulls the first item from the queue, runs the query, and then calls itself.
var myQueueArray = [1, 2, 3];
var currentIndex = 0;
function queueGet() {
if(currentIndex >= myQueueArray.length) {
return;
}
$.get("/my/url/", { 'checkedValue', myQueueArray[currentIndex] }, function(data) {
//this is the important part...call the queue again until it's done
currentIndex += 1;
queueGet();
});
}
The version above is non-destructive of the array. You could of course just pop the array too.

jQuery Ajax / .each callback, next 'each' firing before ajax completed

Hi the below Javascript is called when I submit a form. It first splits a bunch of url's from a text area, it then:
1) Adds lines to a table for each url, and in the last column (the 'status' column) it says "Not Started".
2) Again it loops through each url, first off it makes an ajax call to check on the status (status.php) which will return a percentage from 0 - 100.
3) In the same loop it kicks off the actual process via ajax (process.php), when the process has completed (bearing in the mind the continuous status updates), it will then say "Completed" in the status column and exit the auto_refresh.
4) It should then go to the next 'each' and do the same for the next url.
function formSubmit(){
var lines = $('#urls').val().split('\n');
$.each(lines, function(key, value) {
$('#dlTable tr:last').after('<tr><td>'+value+'</td><td>Not Started</td></tr>');
});
$.each(lines, function(key, value) {
var auto_refresh = setInterval( function () {
$.ajax({
url: 'status.php',
success: function(data) {
$('#dlTable').find("tr").eq(key+1).children().last().replaceWith("<td>"+data+"</td>");
}
});
}, 1000);
$.ajax({
url: 'process.php?id='+value,
success: function(msg) {
clearInterval(auto_refresh);
$('#dlTable').find("tr").eq(key+1).children().last().replaceWith("<td>completed rip</td>");
}
});
});
}
What you want is to run several asynchronous actions in sequence, right? I'd build an array of the functions to execute and run it through a sequence helper.
https://github.com/michiel/asynchelper-js/blob/master/lib/sequencer.js
var actions = [];
$.each(lines, function(key, value) {
actions.push(function(callback) {
$.ajax({
url: 'process.php?id='+val,
success: function(msg) {
clearInterval(auto_refresh);
//
// Perform your DOM operations here and be sure to call the
// callback!
//
callback();
}
});
});
}
);
As you can see, we build an array of scoped functions that take an arbitrary callback as an argument. A sequencer will run them in order for you.
Use the sequence helper from the github link and run,
var sequencer = new Sequencer(actions);
sequencer.start();
It is, btw, possible to define sequencer functions in a few lines of code. For example,
function sequencer(arr) {
(function() {
((arr.length != 0) && (arr.shift()(arguments.callee)));
})();
}
AJAX is asynchronous.
That's exactly what's supposed to happen.
Instead of using each, you should send the next AJAX request in the completion handler of the previous one.
You can also set AJAX to run synchronously using the "async" property. Add the following:
$.ajax({ "async": false, ... other options ... });
AJAX API reference here. Note that this will lock the browser until the request completes.
I prefer the approach in SLaks answer (sticking with asynchronous behavior). However, it does depend on your application. Exercise caution.
I would give the same answer as this jquery json populate table
This code will give you a little idea how to use callback with loops and ajax. But I have not tested it and there will be bugs. I derived the following from my old code:-
var processCnt; //Global variable - check if this is needed
function formSubmit(){
var lines = $('#urls').val().split('\n');
$.each(lines, function(key, value) {
$('#dlTable tr:last').after('<tr><td>'+value+'</td><td>Not Started</td></tr>');
});
completeProcessing(lines ,function(success)
{
$.ajax({
url: 'process.php?id='+value,
success: function(msg) {
$('#dlTable').find("tr").eq(key+1).children().last().replaceWith("<td>completed rip</td>");
}
});
});
}
//Following two functions added by me
function completeProcessing(lines,callback)
{
processCnt= 0;
processingTimer = setInterval(function() { singleProcessing(lines[processCnt],function(completeProcessingSuccess){ if(completeProcessingSuccess){ clearInterval(processingTimer); callback(true); }})}, 1000);
}
function singleProcessing(line,callback)
{
key=processCnt;
val = line;
if(processCnt < totalFiles)
{ //Files to be processed
$.ajax({
url: 'status.php',
success: function(data) {
$('#dlTable').find("tr").eq(key+1).children().last().replaceWith("<td>"+data+"</td>");
processCnt++;
callback(false);
}
});
}
else
{
callback(true);
}
}

Best way to add a 'callback' after a series of asynchronous XHR calls

I stumbled on a piece of Ajax code that is not 100% safe since it's mixing asynchronous/synchronous type of code... so basically in the code below I have a jQuery.each in which it grabs information on the elements and launch an Ajax get request for each:
$(search).each(function() {
$.ajax({
url: 'save.x3?id='+$(this).attr("id")+'value='$(this).data("value");
success: function(o){
//Update UI
},
error: function(o){
//Update UI
}
});
});
//code to do after saving...
So obviously the 'code to do after saving...' often gets executed before all the requests are completed. In the ideal world I would like to have the server-side code handle all of them at once and move //code to do after saving in the success callback but assuming this is not possible, I changed the code to something like this to make sure all requests came back before continuing which I'm still not in love with:
var recs = [];
$(search).each(function() {
recs[recs.length] = 'save.x3?id='+$(this).attr("id")+'value='$(this).data("value");
});
var counter = 0;
function saveRecords(){
$.ajax({
url: recs[counter],
success: function(o){
//Update progress
if (counter<recs.length){
counter++;
saveRecords();
}else{
doneSavingRecords();
}
},
error: function(o){
//Update progress
doneSavingRecords(o.status);
}
});
}
function doneSavingRecords(text){
//code to do after saving...
}
if (recs.length>0){
saveRecords(); //will recursively callback itself until a failed request or until all records were saved
}else{
doneSavingRecords();
}
So I'm looking for the 'best' way to add a bit of synchronous functionality to a series of asynchronous calls ?
Thanks!!
Better Answer:
function saveRecords(callback, errorCallback){
$('<div></div>').ajaxStop(function(){
$(this).remove(); // Keep future AJAX events from effecting this
callback();
}).ajaxError(function(e, xhr, options, err){
errorCallback(e, xhr, options, err);
});
$(search).each(function() {
$.get('save.x3', { id: $(this).attr("id"), value: $(this).data("value") });
});
}
Which would be used like this:
saveRecords(function(){
// Complete will fire after all requests have completed with a success or error
}, function(e, xhr, options, err){
// Error will fire for every error
});
Original Answer: This is good if they need to be in a certain order or you have other regular AJAX events on the page that would affect the use of ajaxStop, but this will be slower:
function saveRecords(callback){
var recs = $(search).map(function(i, obj) {
return { id: $(obj).attr("id"), value: $(obj).data("value") };
});
var save = function(){
if(!recs.length) return callback();
$.ajax({
url: 'save.x3',
data: recs.shift(), // shift removes/returns the first item in an array
success: function(o){
save();
},
error: function(o){
//Update progress
callback(o.status);
}
});
}
save();
}
Then you can call it like this:
saveRecords(function(error){
// This function will run on error or after all
// commands have run
});
If I understand what you're asking, I think you could use $.ajaxStop() for this purpose.
This is easily solved by calling the same function to check that all AJAX calls are complete. You just need a simple queue shared between functions, and a quick check (no loops, timers, promises, etc).
//a list of URLs for which we'll make async requests
var queue = ['/something.json', '/another.json'];
//will contain our return data so we can work with it
//in our final unified callback ('displayAll' in this example)
var data = [];
//work through the queue, dispatch requests, check if complete
function processQueue( queue ){
for(var i = 0; i < queue.length; i++){
$.getJSON( queue[i], function( returnData ) {
data.push(returnData);
//reduce the length of queue by 1
//don't care what URL is discarded, only that length is -1
queue.pop();
checkIfLast(displayAll(data));
}).fail(function() {
throw new Error("Unable to fetch resource: " + queue[i]);
});
}
}
//see if this is last successful AJAX (when queue == 0 it is last)
//if this is the last success, run the callback
//otherwise don't do anything
function checkIfLast(callback){
if(queue.length == 0){
callback();
}
}
//when all the things are done
function displayAll(things){
console.log(things); //show all data after final ajax request has completed.
}
//begin
processQueue();
Edit: I should add that I specifically aimed for an arbitrary number of items in the queue. You can simply add another URL and this will work just the same.
>> In the ideal world I would like to have the server-side code handle all of them at once and move //code to do after saving in the success callback
You'll need to think about this in terms of events.
Closure's net.BulkLoader (or a similar approach) will do it for you:
http://closure-library.googlecode.com/svn/trunk/closure/goog/docs/class_goog_net_BulkLoader.html
http://closure-library.googlecode.com/svn/trunk/closure/goog/docs/closure_goog_net_bulkloader.js.source.html
See:
goog.net.BulkLoader.prototype.handleSuccess_ (for individual calls)
&
goog.net.BulkLoader.prototype.finishLoad_ (for completion of all calls)

Categories

Resources