I request your answer for a issue I've got (part is an array):
for(i=1;i<part.length;i++){
$("#content").append('<div id="id' + i + '"></div>');
$.get('ajax.php?id=' + i, function(data) {
console.log("cache" + i);
$("#id" + i).html(data);
});
});
The problem is that into $.get function, the i value is the value of I when the loop is ended. As there are 140 row in my Array (part), I will always be 140, not 1 then 2 then 3 ..
How to get the i value in the ajax callback ?
Thanks for reply.
Or alternatively, get a JSON from server and iterate over it:
$.get('ajax.php', { from: 1, to: 140 } ).done(function(data) {
$(data).each(function(index) {
//do something with this and index
});
});
This way you'll have access to index internally and will fire only one request to server, thus not polluting the network.
As the AJAX call is asynchronous, all the callbacks run after the loop.
You can use a function expression to create a separate variable for each iteration:
for(i=1;i<part.length;i++){
$("#content").append('<div id="id' + i + '"></div>');
(function(i){
$.get('ajax.php?id=' + i, function(data) {
console.log("cache" + i);
$("#id" + i).html(data);
});
})(i);
});
Note that your loop will try to fire off as many AJAX requests at a time as your browser will permit, with no guarantee that they'll complete in any particular order.
If you really must fire off 140 AJAX requests, I suggest this, instead:
var i = 1;
(function loop() {
if (i < part.length) {
$("#content").append('<div id="id' + i + '"></div>');
$.get('ajax.php?id=' + i).done(function(data) {
console.log("cache" + i);
$("#id" + i).html(data);
i++;
}, loop);
}
});
Guffa his answer should work, you could also use the $.ajax-version and put async-parameter to false. Check out the documentation to see how to use it.
Edit: It should be noted though that using async: false is a bad practice and will be deprecated.
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();
Thanks to the help from here I could build a loop for posts and resolve the promises in order to handle asynchronous requests. But for some reason the loop to get the resolved promises and according values always misses one step. I tried to apply JotaBe's answer and it worked out, except there is one value missing. Everything else is fine.
The simplified function I call is:
var logs = function (outString, saveCSV) {
var promises = [];
var count = 0;
for (i = 1; i <= maxDevice; i++) {
promises.push($.post(Type1));
promises.push($.post(Type2));
promises.push($.post(Type3));
}
promises.push($.post(Type4));
var promiseResolve = $.when.apply($, promises);
promiseResolve.then(function () {
console.log(promises[1].promise().state());
console.log(promises[2].promise().state());
for (i = 0; i < promises.length; i++) {
promises[i].then(function (data, textStatus) {
var src = this.url;
var arg = arguments;
console.log(i + ": " + textStatus);
if (posttype2 or 3){String1 += data
} else if (posttype4) > 0)
{
String2 += data
} else
{
string3 += data
}
});
}
outString += String3+ "\n" + String2+"\n" + string1;
saveCSV(outString, filename);
});
};
The console.log(i + ": " + textStatus);shows
0: success
2: success
3: success
4: success
5: success
6: success
7: success
8: success
9: success
So i = 1is never resolved, even though console.log(promises[1].promise().state()); states promises[1] IS resolved.
If I set a breakpoint before the promiseResolve the missing promise is handled, though, while delaying the code with a timeout doesn't seem to help.
I also tried to use .done, instead of .then with the same result. The data missing is the largest data package of the loop. It can't be the syntax, since the other promises fetched with the same get in the loop resolve just fine.
So what am I doing wrong, as I can't understand why this one is missing. As far as I understood $when.applyis used to make sure the promises are resolved (which the console log states are).
How to handle this to get ALL values into the outstring?
Edit:
I tried some more console.log lines to check for values, or if something isn't resolved. So a console.log(i); right before the promises[i].then(function (data, textStatus)´ shows i = 1 is called. The row is complete 0, 1, 2... but theconsole.log(i + ": " + textStatus);after the function misses the 1, so it shows ´promises[1].then(function (data, textStatus) {...} is not called.
Logging
console.log(promises[1].promise().state());
console.log(promises[1].responseText);
right before the for.. loop shows the promise state is "resolved" and the responseText shows the string I want to attach to the outstrings. I also tried the proposed solution of Jaromanda X, but it did not help (thanks for the time), neither did using different combinations of using .doneinstead of .thenin either resolve function.
Putting a breakpoint before the promiseResolve.then seems to help, even if I click "run" as fast as I can. Have to try shortening that time, to be sure.
Adding another set of things I tried:
Splitting the posts/gets and the resolve into two functions and using the resolve as callback brought no success, neither did changing i to some unused variable, nor using for promise in promises to initiate the loop. I start running out of ideas to try, even more as promiseResolve returns resolved right at the promiseResolve.then function, so the request in promise[1] should be finished.
What seems to work, though I do not understand why and doesn't feel like the right way to solve the problem is encapsulating everything inside the promiseResolve.then function into a window.setTimeout(function(){...},1, so it looks more like
window.setTimeout(function(){
for (i=0 ; i < promises.length; i++) {
...
};
outString += spotString + "\n" + ioString + "\n" + logString;
saveCSV(outString, filename);
}, 1);
So, this one ms delay helps, but it doesn't feel like a clean solution. Can anyone explain why, or what I am missing? There must be a better way.
you have asynchronous code within the for loop that is using i
try this code
promiseResolve.then(function () {
console.log(promises[1].promise().state());
console.log(promises[2].promise().state());
for (i = 0; i < promises.length; i++) {
(function(i) { // ***** add this
promises[i].then(function (data, textStatus) {
var src = this.url;
var arg = arguments;
console.log(i + ": " + textStatus);
if (posttype2 or 3) {
String1 += data
} else if (posttype4) > 0) {
String2 += data
} else {
string3 += data
}
});
}(i)); // ***** add this
}
outString += String3 + "\n" + String2 + "\n" + string1;
saveCSV(outString, filename);
});
You'll have to make the changes from the invalid javascript to your actual code as above ... the additional lines are marked
what that code does is create a closure where i wont be changed before it is logged due to the asynchronous nature of the function it is logged in
I am having a synchronisation issue in Javascript. Code below. When I make the call to get mutual friends, although my function is still filling the array through the API callback, the printing of "Quite a bunch: 0" happens before the printing of the - console.log(friendID +" -> " + mutualfriends.data.length);
I know this must be a callback / asynch issue but I have no idea how to deal with it. I'm filling the array for a reason - need it to be filled for the next part.
code:
function getMutualFriends(friendID)
{
//console.log(response.data[i].id);
try{
FB.api('/me/mutualfriends/'+friendID, function(mutualfriends) {
//console.log(mutualfriends);
//console.log(mutualfriends.data.length);
console.log(friendID +" -> " + mutualfriends.data.length);
mutualFriendsList.push([friendID,mutualfriends.data.length]);
});
}
catch(err){
console.log('error caught: ' +err);
}
}
function getFriendsList()
{
FB.getLoginStatus(function(response){
FB.api('/me/friends', function(response) {
for(i=0; i<response.data.length;i++)
{
var friendID = response.data[i].id;
console.log(friendID);
friendsList.push(friendID);
}
console.log('Interesting, we gathered: '+friendsList.length+' friends,');
console.log('lets check mutual friends now');
for(j=0; j<friendsList.length;j++)
{
getMutualFriends(friendsList[j]);
}
console.log('Quite a bunch: ' + mutualFriendsList.length);
});//friends
});
}
You'll probably want to turn your "post-condition" code into a callback.
So where you now have:
for(j=0; j<friendsList.length;j++)
{
getMutualFriends(friendsList[j]);
}
console.log('Quite a bunch: ' + mutualFriendsList.length);
You'll want something like:
for(j=0; j<friendsList.length;j++)
{
getMutualFriends(friendsList[j], function(mutualFriendsList) {
console.log('Quite a bunch: ' + mutualFriendsList.length);
});
}
When you set it up like this, your getMutualFriends function can call the callback once it got the result:
function getMutualFriends(friendID, callback)
{
FB.api('/me/mutualfriends/'+friendID, function(mutualfriends) {
mutualFriendsList.push([friendID,mutualfriends.data.length]);
callback(mutualFriendsList);
});
}
This will do the callback once for every call to getMutualFriends. If you want the callback to only trigger once for all friends, you'll need to expand the concept a bit further.
Update:
You could combine the above "per friend" callback with #RGDev's condition to detect the last friend:
for(j=0; j<friendsList.length;j++)
{
getMutualFriends(friendsList[j], function(mutualFriendsList) {
if (mutualFriendsList.length == friendsList.length) {
console.log('Quite a bunch: ' + mutualFriendsList.length);
}
});
}
getMutualFriends is doing an async call on his own (by calling FB.api) so you cannot print the mutualFriendsList until it's done (actually, you are doing may calls to getMutualFiends so your mutualFriendsList won't be accurate until all of them finished their async processing).
Can't you rely on that collection to be async? I don't know what you are doing with that, but maybe (if you are drawing it in the screen by example) you can issue a redraw of that component every time the callback of FB.api('/me/mutualfriends/'+friendID, ...) is done.
I had a similar issue to you in the past, I solved it by using a backbone.js' collection instead of a simple javascript array. Then I bound a listener to the add event of the collection and print the qty of friends every time an object was added to it.
Good luck,
You could use a while loop to wait until the mutualFriendsList.length is equal to friendsList.length. which would mean that all of the ajax functions return functions had been fulfilled.
It would look something like:
while(mutualFriendsList.length != friendsList.length)
{
console.log("Still Waiting");
}
console.log('done')
// do your data processing
A little inelegant, but it should work
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
SOLVED - Due to a Simple Mistake in the HTML that messed up the associated Javascript
I'm trying to issue a callback using ".ajaxStop" after a series of many AJAX requests have been issued. However, my code is not invoking the callback after the requests have finished.
Here is my code:
$.getJSON('https://graph.facebook.com/me/mutualfriends/' +
friendArray[i] + "?access_token=" + accessToken,
(function(index) {
for (var i = 0; i < friendArray.length; i++){
return function(dataJSON2) {
resultArray = dataJSON2['data'];
resultJSON += '"' + friendArray[index] + '"' + ":" + resultArray.length;
if (index != friendArray.length - 1){
resultJSON += ",";
}else {
resultJSON += "}";
}
}
}) (i));
}
$('#messageContainer').ajaxStop( function() {
$(this).html(resultJSON);
});
Each of the callbacks for my AJAX requests are correctly put in a closured callback but when I wait for the ajaxStop method to trigger, nothing happens.
You have a scope and timing issue. 'Scope' because the variable resultJSON only exists within your $.getJSON callback method. 'Timing' because even if you managed to share the resultJSON variable, due to the asynchronous nature of the $.getJSON method, the resultJSON variable wouldn't populate in time to be used inside the $.ajaxStop method.