While looping a jquery ajax call - javascript

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();

Related

Javascript Async/Await - Cannot get it to work

My 1st post. I'm a Javascript 'hacker' (and feel much more comfortable with PHP, never gotten a hang of Javscript):
I've tried for hours trying callbacks, delays, now ASYNC/AWAIT:
Problem: AWAIT Code Not doing what I want.
I've referenced multiple examples and forums, but not turn to stack overflow in desperate frustration!
Note: Expanding on this existing tool: https://github.com/webismymind/editablegrid/tree/master/examples
(notice it says be sure to run AJAX in Synchronous)
DatabaseGrid.prototype.initializeGrid = function(grid) {
var self = this;
grid.setEnumProvider('id_Bolt', new EnumProvider({
// the function getOptionValuesForEdit is called each time the cell is edited
// here we do only client-side processing, but you could use Ajax here to talk with your server
// if you do, then don't forget to use Ajax in synchronous mode
getOptionValuesForEdit: **async** function (grid, column, rowIndex) {
var Thread_Code = grid.getValueAt(rowIndex, grid.getColumnIndex("Thread_Code"));
**let** result = **new** setBoltEnum(Thread_Code);
*console.log(Date.now() + " - 3 - " + **await** result.array);*
return **await** result.array;
*console.log(Date.now() + " - 4 - " + "ending function");*
}
}));
};
AJAX code it is calling:
setBoltEnum = function(Thread_Code){
*console.log(Date.now() + " - 1 - calling AJAX");*
result = $.ajax({
url: 'EnumHelper.php',
type: 'POST',
dataType: "html",
data: {
value: Thread_Code,
col_out: 'Descr',
col_search: 'Thread_Code',
tablename: 'Bolt_List'
},
success: function (response) {
result = JSON.parse(response);
*console.log(Date.now() + " - 2 - got AJAX response: " + result.resp);*
return result;
//_callback();
},
error: function(XMLHttpRequest, textStatus, exception) { alert("Ajax failure\n" + errortext); },
async: true
});
};
Output in Chrome:
1 - calling AJAX
3 - undefined
2 - Got AJAX response - ok
(4) is not outputted
How can I get code to run 1-2-3-4?
You can only usefully await a promise.
You call setBoltEnum with new so it is treated as a constructor function and returns an object that is an instance of setBoltEnum (this doesn't make any sense because that function doesn't do anything that would be useful to use a constructor function for). It certainly doesn't return a promise.
setBoltEnum then calls $.ajax (which runs asynchronously) and assigns the result to result (You don't seem to have declared result so it is a global… this is probably bad, especially when dealing with asynchronous code). It then does nothing with that variable. It has no return statement at all so even if you didn't call it with new it will wouldn't return a promise.
Then you return await result.array;. There's no point in awaiting as you return since you are going to be returning a promise anyway (because the function is async). Here, result is the return value of $.ajax() which won't have an array property so the value is undefined (not a promise).
The next line is console.log but you never reach it because you just returned.
Later the success function is called. This assigns a new value to result which might have an array property… but that also won't be a promise.
To fix this:
Don't use new when you aren't dealing with a constructor function
Don't use await when you aren't dealing with a promise or other thenable
Do return promises or other thenables from functions that work asynchronously
Don't mix callbacks (success / error) with promises (it just makes code hard to manage)
Do remember that $.ajax() returns a thenable (which you can await).
Below code ended up working and outputted 1-2-3. Note, I stumbled upon this (adding ASYNC/AWAIT in setBoltEnum).
setBoltEnum = async function(Thread_Code){
console.log(Date.now() + " - 1 - calling AJAX");
return await $.ajax({
url: 'EnumHelper.php',
type: 'POST',
dataType: "html",
data: {
value: Thread_Code,
col_out: 'Descr',
col_search: 'Thread_Code',
tablename: 'Bolt_List'
},
success: function (response) {
result = JSON.parse(response);
console.log(Date.now() + " - 2 - got AJAX response: " + result);
},
error: function(XMLHttpRequest, textStatus, exception) { alert("Ajax failure\n" + errortext); },
async: true
});
};
DatabaseGrid.prototype.initializeGrid = function(grid) {
var self = this;
grid.setEnumProvider('id_Bolt', new EnumProvider({
// the function getOptionValuesForEdit is called each time the cell is edited
// here we do only client-side processing, but you could use Ajax here to talk with your server
// if you do, then don't forget to use Ajax in synchronous mode
getOptionValuesForEdit: async function (grid, column, rowIndex) {
var Thread_Code = grid.getValueAt(rowIndex, grid.getColumnIndex("Thread_Code"));
result = await setBoltEnum(Thread_Code);
console.log(Date.now() + " - 3 - returning this: " + result);
return result;
//return expecting this { "br" : "Brazil", "ca": "Canada", "us" : "USA" }; //result;
}
}));

More JQuery/Ajax and when/done/promise confusion

Once again I struggle with ajax calls - this time around some chaining issue. Overall here is what I need to accomplish:
I loop over some array, and for each item in the array, I need to do the following:
Issue an Ajax call, and upon success, I need to issue three other calls, which must be chained, so they run in sequence.
When all the items in the array have both their main call and the three chained subcalls completed, I must be able to do some action.
My problem is, that the program does not wait for the three chained subcalls to complete. In the code below, this can be seen by the "Done" statement in the log turns up before the subcalls have completed.
I have created a JSFiddle here: https://jsfiddle.net/LeifFrederiksen/td534phz/1/
Note: I have two different function for the addAttachments function (addAttachments and addAttachmentsAlternative) - none of them works like they should.
var items = ["A","B"];
save();
function doneSaving() {
log("<H1>Done</H1>");
}
function save() {
// Save all items, and do something when all is done...
log("<H1>Save initiated</H1>");
var returnValue = saveItems();
$.when(returnValue).done(function() {
doneSaving();
})
}
function saveItems() {
// Loop through all items and save each of them...
var requests = Array();
// Build array of requests to wait for...
for (item of items) {
requests.push(saveOneItem(item));
}
var returnValue = $.when.apply($, requests).done(function() {
log("All requests completed");
})
return returnValue;
}
function saveOneItem(item) {
// Save one item...
return addListItem(item,addListItemSuccess,addListItemFailure);
}
function addListItem(item, successFunction, failureFunction) {
// The actual ajax that handles saving to database (actually Sharepoint via REST)...
log("addListItem on: " + item);
var returnValue =
$.ajax({
url: "/echo/json/",
data: {html: item,
delay: 1},
}).done(function (data) {
if (successFunction != undefined) {
returnValue = successFunction(item, data); // Returns the newly created list item information
return returnValue;
}
}).fail(function (data) {
return failureFunction(item, data);
});
return returnValue;
}
function addListItemSuccess(item,data) {
log("addListItem succces - in succes function for " + item);
returnValue = addAttachmentsAlternative(item,data);
return returnValue;
}
function addAttachments(item,data) {
var attachment1Deferred = addListItem(item + "-attachment 1",addAttachmentSuccess,addAttachmentFailure);
var attachment2Deferred = attachment1Deferred.then(
function() {
return addListItem(item + "-attachment 2",addAttachmentSuccess,addAttachmentFailure);
});
var attachment3Deferred = attachment2Deferred.then(
function() {
return addListItem(item + "-attachment 3",addAttachmentSuccess,addAttachmentFailure);
});
attachment3Deferred.done(
function() {
log("Completed upload of all attachments for " + item);
})
return attachment3Deferred;
}
function addAttachmentsAlternative(item,data) {
return addListItem(item + "-attachment 1",addAttachmentSuccess,addAttachmentFailure)
.done(function(data) {
return addListItem(item + "-attachment 2",addAttachmentSuccess,addAttachmentFailure)
}).done(function(data) {
return addListItem(item + "-attachment 3",addAttachmentSuccess,addAttachmentFailure)
}).done(function(data) {
log("Completed alternative upload of all attachments for " + item);
});
}
function addAttachmentSuccess(item,data) {
log("addAttachment succces - in succes function for " + item);
var deferred = $.Deferred();
deferred.resolve();
return deferred;
}
function addListItemFailure(item,data) {
console.log("addListItem failed - calling failure function for " + item);
$("#console").append("<P>addListItem failed - in failure function for " + item);
}
function addAttachmentFailure(item,data) {
console.log("addListItem failed - calling failure function for " + item);
$("#console").append("<P>addListItem failed - in failure function for " + item);
}
function log(message) {
console.log(message);
$("#console").append("<P>" + message);
}
I am hoping to achieve some generic pattern that I can use in different cases.
I got my inspiration from this great article, but cannot get it to work in my scenario: https://medium.com/coding-design/writing-better-ajax-8ee4a7fb95f#.tu0sruz5k
Any ideas and inputs are more than welcome.
Regards
Leif
There are several issues with the provided example:
to chain the tasks of creating list items and adding attachments use
.then instead of .done. With .done callback that prints All requests completed it is fired once deferred (first ajax call in addListItem function) is getting resolved.
some functions like addListItem still uses callback function syntax, i would suggest convert them to promises
since all deferred are getting resolved in saveItems function there is no need to use jQuery.when() in save function
Modified demo

Making multiple ajax requests synchronously

Let's suppose I have some function called makeRequest(), which makes an AJAX request to a server.
Now let's suppose I am given the amount of times this request should be made, but I can't do them asynchronously but synchronously instead.
For instance, I am given the number 5, and I shall call makeRequest(), when it's done, I shall call it again, and when it's done, I shall call it again... I should end up calling it 5 times.
I'm no expert at JavaScript but I found it easy to handle asynchronous calls by the use of callbacks.
So, my makeRequest() function takes a callback argument that is to be executed when the request has succeeded.
In my previous example, I had to make the request 5 times, so the behaviour should look like:
makeRequest(function () {
makeRequest(function () {
makeRequest(function () {
makeRequest(function () {
makeRequest(function () {
});
});
});
});
});
How can I design this to behave the same for any argument given to me, be it 6, 12 or even 1?
PS: I have tried many approaches, the most common involving creating while loops that wait until a flag is set by a finished request. All of these approaches makes the browser think the script crashed and prompt the user to stop the script.
Simple, recursively call the ajax request, while keeping track of a count variable:
function makeRequest(count, finalCallback){
someAjaxCall(data, function(){
if(count > 1){
makeRequest(count - 1, finalCallback);
} else {
finalCallback && finalCallback();
}
});
}
finalCallback is a optional callback (function) that will be executed when all the requests are completed.
You can do it this way,
var i = 5; // number of calls to you function calling ajax
recurs(i); // call it initially
function recurs(count) {
makeRequest(function() {
count--; // decrement count
if (count > 1) {
recurs(count) // call function agian
}
});
}
Here I have written multiple Ajax calls using promises. This function will run synchronously. You can get the current position of response which is executed from Ajax.
var ajaxs = {
i : 0,
callback : null,
param : null,
exec_fun : function (i) {
let data_send = this.param[i];
let url = this.url;
this.promise = new Promise(function (res,rej) {
$.ajax({
url: url,
method: 'POST',
data: data_send,
dataType: 'json',
success: function(resvalidate){
res(resvalidate);
}
});
});
this.promise.then(function (resvalidate) {
let resp = resvalidate,
param = ajaxs.param,
pos = ajaxs.i,
callback_fun = ajaxs.callback_fun;
callback_fun(resp,ajaxs.i);
ajaxs.i++;
if( ajaxs.param[ajaxs.i] != undefined){
ajaxs.exec_fun(ajaxs.i);
}
});
},
each : function (url,data,inc_callback) {
this.callback_fun = inc_callback;
this.param = data;
this.url = url;
this.exec_fun(ajaxs.i);
}
};
let url = "http://localhost/dev/test_ajax.php";
let data_param = [{data : 3},{data : 1},{data : 2}];
ajaxs.each(url,data_param, function (resp,i) {
console.log(resp,i);
});

Typescript sequence of a Class methods

I got a typescript class with an attribute that contain some connection data that are the result of an ajax call, here's a snippet:
class User {
// ... other stuff (constructor, attributes, methods)
static data:{id:number; token:string} = {id: 0, token: ""};
connect() {
// Ajax Call
.done(function(e){
User.data.id = e.id;
User.data.token = e.token;
})
}
request() {
if(User.data.id == 0)
setTimeout(() => {
this.request();
}, 500);
else return '?id=' + User.data.id + '&token=' + User.data.token;
}
}
I tried to use connect() and subsequently request() but sometimes the request() function starts before ajax's answer. Now I'm trying to write the request() function with some waiting time and a sort of recursion. Unfortunately it doesn't work.. My goal is to call the request() function and obtain the string only when "id" and "token" are ready (not 0 and empty string). Any suggestion would be appreciated!
PS: I can't put the request() function inside the ajax callback: the two functions should be separated
The calling code should be combining the promises with then. connect() and request() should just return their promises. e.g. they "promise to return done when connected" and "promise to return the requested data". In the case of request, it does not need a deferred object or promise as it simply returns a value immediately.
JSFiddle example: http://jsfiddle.net/TrueBlueAussie/qcjrLv4k/3/
// Return a fake connection after 5 seconds
var connect = function (){
var def = $.Deferred();
setTimeout(function(){
def.resolve();
}, 5000);
return def.promise();
}
var request = function(){
return "?data=d";
}
and use like this:
connect().then(request).done(function(data){
alert(data);
});
So in your case, just return the ajax call result as a promise from connect():
connect() {
return $.ajax({[snipped]})
.done(function(e){
User.data.id = e.id;
User.data.token = e.token;
});
}
and request simply returns the string:
request() {
return '?id=' + User.data.id + '&token=' + User.data.token;
}
Another way to do it is to save the connect promise:
var promise = connect();
and use it whenever you want to get the request data:
promise.then(request).done(function(data){
alert(data);
});
A better way, if your request is dependent on a "connection", is to pass the connect promise to the request method as a required parameter:
request(connectionPromise){
return connectionPromise.then(function(){
return '?id=' + User.data.id + '&token=' + User.data.token;
});
};
and call with:
request(connect()).done(function(data){
alert(data);
});
Example using this approach here: http://jsfiddle.net/TrueBlueAussie/qcjrLv4k/4/
Final example (based on commented desire to reuse connection). Combine the previous answers like this:
1) Save the connection as a property of the object.
// Start the connection process and save the promise for re-use
var connectionPromise = connect();
2) Use the connection as a parameter to the requests (so they do not need to have knowledge of the external variable):
// Make a request, passing the connection promise
request(connectionPromise).done(function(data){
alert(data);
});
3) The request method does not change from the previous example:
request(connectionPromise){
return connectionPromise.then(function(){
return '?id=' + User.data.id + '&token=' + User.data.token;
});
};
Once you are in promise land you need to stay there (or use a callback):
class User {
// ... other stuff (constructor, attributes, methods)
static data: { id: number; token: string } = { id: 0, token: "" };
private connectedPromise;
connect() {
// Ajax Call
this.connectedPromise = something.done( function ( e ) {
User.data.id = e.id;
User.data.token = e.token;
})
}
request() {
this.connectedPromise.then( () => {
if ( User.data.id == 0 )
setTimeout( () => {
this.request();
}, 500 );
else return '?id=' + User.data.id + '&token=' + User.data.token;
})
}
}
Store the result of connect in a var on only execute request if connect has resolved.
PS the request function is bad (I haven't cleaned it up but removed the return) in that it is maybe async i.e. it either returns a value or does some async work. Better to be consistent and always be async.

(Why) does this ajax queue overwrite response data?

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

Categories

Resources