This question already has answers here:
Queue AJAX calls
(2 answers)
Closed 3 years ago.
How can synchrony be forced in a javascript loop?
This is without synchrony :
$.each(data, function(index, value) {
alert(index + ': ' + value);
});
synchrony attempt :
var time = 500;
$.each(data, function(index, value) {
setTimeout(alert(index + ': ' + value), time);
time += 500;
});
but so far all it does is run the alert in a row, without interval
This is the ajax request that I need to run in a QUEUE :
$.each(data, function(key, val) {
$.ajax({
url: 'ajax/getPerson.php',
type:'post',
dataType: 'json',
data: { 'dude' : val["idG"] },
success: function(data) { createHTML.person(data) }
}
Try this:
var time = 500;
$.each(data, function(index, value) {
setTimeout(function() {
alert(index + ': ' + value);
}, time);
time += 500;
});
Pass the code you want to execute in a function() object as first argument to setTimeout(), like so:
var time = 500;
$.each(data, function(index, value) {
setTimeout(function() { alert(index + ': ' + value) }, time);
time += 500;
});
According to your comment:
I am using this function to make a DataBase Requests and they are not coming back in the order I ask for
The callback method is intuitive and rather easier to implement, but what if you have 100 request to be made? Assuming 200ms per response-time that's total of 20 second.
So here is the question: Do you really care about the returning order of AJAX calls or you just have to process the returning data by the order of calls you have fired?
If the answer is latter, the solution will be:
fire all ajax calls at once ( or in queue )
maintain an array of returning datas
check array whether all requests have been successfully returned, re-request if there's an error ( or other error handling mechanism)
when all requests are returned, do the data processing you originally intended
Related
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();
If I make an ajax call, I can add success handling. I want to add similar logic to my custom functions.
I have 6-10 custom functions that MUST be run sequentially or independently. They don't typically run independently so I have them daisy-chained now by calling the next function at the end of the previous but that is messy to read and does not allow for separate execution.
I would love to have something like this:
function runall(){
runfirst().success(
runsecond().success(
runthird()
))
}
I have had other situations were I would like to add .success() handling to a custom function, but this situation made it more important.
If there is another way to force 6-10 functions to run synchronously, that could solve this problem, but I would also like to know how to add success handling to my custom functions.
I tried the following based on #lanzz's suggestion:
I added .then() to my function(s):
$bomImport.updateGridRow(rowId).then(function () {
$bomImport.toggleSubGrid(rowId, false);
});
var $bomImport = {
updateGridRow: function (rowId) {
$('#' + rowId + ' td[aria-describedby="bomImport_rev"]').html($("#mxRevTxt").val());
$('#' + rowId + ' td[aria-describedby="bomImport_itemno"]').html($("#itemNoTxt").val());
$('#' + rowId + ' td[aria-describedby="bomImport_used"]').html($("#usedTxt").val());
$('#' + rowId + ' td[aria-describedby="bomImport_partSource"]').html($("#partSourceTxt").val());
$('#' + rowId + ' td[aria-describedby="bomImport_partClass"]').html($("#partClassTxt").val());
$('#' + rowId + ' td[aria-describedby="bomImport_partType"]').html($("#partTypeTxt").val());
$('#' + rowId + ' td[aria-describedby="bomImport_partno"]').html($("#mxPnTxt").val());
$('#' + rowId + ' td[aria-describedby="bomImport_descript"]').html($("#descTxt").val());
$('#' + rowId + ' td[aria-describedby="bomImport_qty"]').html($("#qtyTxt").val());
$('#' + rowId + ' td[aria-describedby="bomImport_custPartNo"]').html($("#custPartNoTxt").val());
$('#' + rowId + ' td[aria-describedby="bomImport_crev"]').html($("#custRevTxt").val());
$('#' + rowId + ' td[aria-describedby="bomImport_u_of_m"]').html($("#uomTxt").val());
$('#' + rowId + ' td[aria-describedby="bomImport_warehouse"]').html($("#warehouseTxt").val());
$('#' + rowId + ' td[aria-describedby="bomImport_standardCost"]').html($("#stdCostTxt").val());
$('#' + rowId + ' td[aria-describedby="bomImport_workCenter"]').html($("#wcTxt").val());
var defferred = new $.Deferred();
return defferred.promise();
}};
The code correctly goes to the end of updateGridRow, gives no errors, but never gets back to call the second function.
I also tried the following as was suggested #Anand:
workSheetSaveExit(rowId, isNew).save().updateRow().toggle();
function workSheetSaveExit(){
this.queue = new Queue;
var self = this;
self.queue.flush(this);
}
workSheetSaveExit.prototype = {
save: function () {
this.queue.add(function (self) {
$bomImport.workSheetSave(rowId, isNew);
});
return this;
},
updateRow: function () {
this.queue.add(function (self) {
$bomImport.updateGridRow(rowId);
});
return this;
},
toggle: function () {
this.queue.add(function (self) {
$bomImport.toggleSubGrid(rowId, false);
});
return this;
}
};
Which didn't work.
Final Solution
For a great explanation of how to use deferred and make this work see here:
Using Deferred in jQuery
How to use Deferreds:
function somethingAsynchronous() {
var deferred = new $.Deferred();
// now, delay the resolution of the deferred:
setTimeout(function() {
deferred.resolve('foobar');
}, 2000);
return deferred.promise();
}
somethingAsynchronous().then(function(result) {
// result is "foobar", as provided by deferred.resolve() in somethingAsynchronous()
alert('after somethingAsynchronous(): ' + result);
});
// or, you can also use $.when() to wait on multiple deferreds:
$.when(somethingAsynchronous(), $.ajax({ something })).then(function() {
alert('after BOTH somethingAsynchronous() and $.ajax()');
});
If your functions simply make an AJAX request, you can just return the actual promise returned by $.ajax():
function doAjax() {
return $.ajax({ /* ajax options */ });
}
doAjax().then(function() {
alert('after doAjax()');
});
From what I can tell you really just want a better way to organize these callbacks. You should use a FIFO array or a queue. Your run all should do your stacking for you then execute the first function.
var RunQueue = function(queue){
this.init(queue);
}
var p = RunQueue.prototype = {};
p.queue = null;
p.init = function(queue){
this.queue = queue.slice(); //copy the array we will be changing it
// if this is not practical, keep an index
}
p.run = function(){
if(this.queue && this.queue.length) {
var first = this.queue[0];
this.queue.shift();
var runQueue = this;
first(function(){ /*success callback parameter*/
runQueue.run();
});
}
}
Usage:
var queue = [runFirst, runSecond, runThird, ...]
(new RunQueue(queue)).run();
If you really want to get fancy, and you may need to, you could pass in Objects in the array containing your parameters and have RunQueue append the last parameter as the success callback. You could even pass in the context to run the function in that object then call apply or call (whichever one uses the array) on your method.
{
method: runFirst,
context: someObject,
parameters: [param1, param2, param3];
}
If Each of your function returns a state/function and then probably you could add a prototype to each state/function, then you would be able to call the functions like this, in fluent api way(method chaining).
runfirst().runSecond().runThird()
and so on.
Lemme try to build a sample.
EDIT
See this, if it fits your design
EDIT 2
I did not realise, you were talking about async method chaining.
There is very good example here. It was discussed in this stackoverflow thread
I work with JSON and I want to count the number of elements in the response.
$.getJSON("/api/getEvents", function(data) {
$.each(data, function(key, event) {
var count = 10;
$.getJSON("/api/getUsers", function(data) {
$.each(data, function(key, event) {
alert("Value: " + count);
count++;
});
});
alert("Count: " + count);
});
});
As a result, I get:
Value: 10
Value: 11
Value: 12
...
Count: 10
Why count = 10?
It's because ajax requests are asynchronous. $.getJSON just initiates a request, but javascript execution immediately continues. You can see the count if you move the alert inside the ajax callback:
$.getJSON("/api/getEvents", function(data) {
$.each(data, function(key, event) {
var count = 10;
$.getJSON("/api/getUsers", function(data) {
$.each(data, function(key, event) {
alert("Value: " + count);
count++;
});
// I moved this here:
alert("Count: " + count);
});
// It used to be here.
});
});
So after you set var count = 10, the javascript parser then runs $.getJSON, but then immediately goes on to the next line, which in your code example alerted the "Count: 10". Then, whenever the request finishes, it runs the callback code that increments the count and alerts the Value lines.
For some reason my values are not being stored in the array:
var req = new Array();
$.get('./ajax/get_cat_info.php?cid=' +cid, function(data, textStatus) {
var count = 0;
$.each(data, function(key, val) {
$('#' + key).show();
if(val == 1) {
req[count] = key;
count = count + 1;
//var arLen=req.length;
//alert('l: ' + arLen); // this works though
}
});
}, 'json');
var arLen=req.length;
alert('l: ' + arLen);
I get alerted "l: 0" at the end. If I uncomment the line alert in the IF statement, it alerts on each one, then still alerts 0.
AJAX requests are, by default, asynchronous. You'll either have to change the AJAX request to be synchronous, or use the value of req in the callback.
In addition, you might want to use req.push(key) rather than using a count variable and req[count] = key; (although this isn't your problem).
The get call is running asynchronously, and so arLen=req.length is being evaluated prior to the function of elements being set actually completing. You can set the values accordingly from within the callback of the async call, as you determined.
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