Callbacks on Multiple AJAX request termination - javascript

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.

Related

Only Push success callback data to array, exclude error 404

I have issues with handling error 404. What I want to do is, the data I get from making a request to the PokeApi, I want to push it into an array. If all request are successful to the API and pushed into the array, everything works fine, then I can get out my data I want, move it to my then() and append it to a div. However, once a request are getting a 404 error not found, nothing gets appended to my div. How do I handle the 404 and continue to loop and be able to append my data?
I have tried, using if statements and fail(), done() but just doesn't work as I want it to.
function get_sprites(poke_name){
var arr = [];
for(var i = 0; i<poke_name.length; i++){
arr.push($.getJSON("https://pokeapi.co/api/v2/pokemon/" +
poke_name[i]));
}
$.when.apply($, arr).then(function(){
var storeObjPoke = [];
for(var i = 0; i < arguments.length; i++){
var name = arguments[i][0].name
var upperCase_name = name.charAt(0).toUpperCase() +
name.slice(1);
var objPoke = {
id : arguments[i][0].id,
name : upperCase_name,
imgUrl : arguments[i][0].sprites.front_default
}
$("<div class='pokemon' id='"+ arguments[i][0].id +"'>" +
upperCase_name + "<br><img src='" +arguments[i]
[0].sprites.front_default+"'alt=''/><br>" + arguments[i][0].id +
"</div>" ).appendTo("#display_pokemon");
}
}
}
I expect it to be able to display all my objects in my div, but when I get a 404 error, nothing gets appended to div.
So it looks like .when is similar to promise.all with the fact that it's an all or nothing kinda thing. All promises have to be resolved (successful) for the .then function to be fired.
So what is happening for you is once the promise with the 404 is rejected, jQuery calls the .fail() function immediately (which you don't have so its swallowing the error) and the rest of the promises may be unfulfilled and .then() is not called.
From jQuery.when()
In the case where multiple Deferred objects are passed to jQuery.when(), the method returns the Promise from a new "master" Deferred object that tracks the aggregate state of all the Deferreds it has been passed. The method will resolve its master Deferred as soon as all the Deferreds resolve, or reject the master Deferred as soon as one of the Deferreds is rejected.
and
In the multiple-Deferreds case where one of the Deferreds is rejected, jQuery.when() immediately fires the failCallbacks for its master Deferred. Note that some of the Deferreds may still be unresolved at that point.
so what you are trying to do doesn't seem possible without some extra fluff. You can check out this SO answer to get a more general understanding of some of the fluff you might need Wait until all ES6 promises complete, even rejected promises including a future proposal promise. allSettled() which would solve your problem, but its only compatible with a few browsers currently.
2 other thoughts
Is it important that you wait for all requests to come back before you start displaying them in the UI? If not then just call the requests in a loop.
For example,
get_sprites(["squirtle", "charmander", "not_a_pokemon", "bulbasaur"])
function get_sprites(poke_name) {
for (let i = 0; i < poke_name.length; i++) {
$.getJSON("https://pokeapi.co/api/v2/pokemon/" + poke_name[i], addPokemon)
.fail(function(err) {
console.log(`error getting pokemon: ${poke_name[i]} - ${err.status}`);
});
}
function addPokemon(pokemon) {
var storeObjPoke = [];
var name = pokemon.name
var upperCase_name = name.charAt(0).toUpperCase() + name.slice(1);
var objPoke = {
id: pokemon.id,
name: upperCase_name,
imgUrl: pokemon.sprites.front_default
}
$("<div class='pokemon' id='" + pokemon.id + "'>" +
upperCase_name + "<br><img src='" + pokemon.sprites.front_default + "'alt=''/><br>" + pokemon.id +
"</div>").appendTo("#display_pokemon");
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="display_pokemon"></div>
If it is important to not show anything until all requests are finished, just keep a counter on the requests finished using .always() on the request and check after each request if the counter equals poke_name.length. If so, then you know a request for each pokemon has finished (success or fail) and then display all the results.
example of 2
get_sprites(["squirtle", "charmander", "not_a_pokemon", "bulbasaur", "onix", "mewtwo", "pikachu"])
function get_sprites(poke_names) {
let poke_names_length = poke_names.length;
let finished_requests = 0;
let poke_array = [];
for (let poke_name of poke_names) {
$.getJSON("https://pokeapi.co/api/v2/pokemon/" + poke_name)
.always(function() {
finished_requests++
})
.done(addPokemon)
.fail(function(err) {
console.log(`error getting pokemon: ${poke_name} - ${err.status}`);
})
}
function addPokemon(pokemon) {
let upperCase_name = pokemon.name.charAt(0).toUpperCase() + pokemon.name.slice(1);
let objPoke = {
id: pokemon.id,
name: upperCase_name,
imgUrl: pokemon.sprites.front_default
}
poke_array.push(objPoke);
if (poke_names_length === finished_requests) {
displayPokemon();
}
}
function displayPokemon() {
let ordered_pokes = poke_array.sort(function(a, b) {
return a.id - b.id;
});
for (let poke of ordered_pokes) {
$("<div class='pokemon' id='" + poke.id + "'>" +
poke.name + "<br><img src='" + poke.imgUrl + "'alt=''/><br>" + poke.id + "</div>").appendTo("#display_pokemon");
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="display_pokemon"></div>

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

Promise resolve misses one value for asynchronous request loop

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

Ajax function into a loop

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.

Javascript: Arrays

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.

Categories

Resources