Ajax calls in for loop - ajax

I have the function shown below, it just makes a request to a bunch of endpoints housed in an array. Right now I have the Ajax calls be sync (as opposed to the native async).
This is because although when async it does cycle through the urls and makes the calls correctly, the name of the API, which I set to the variable 'name', will only display the last name in the list:
When it should display like this (which it does only if I set the Ajax call to sync):
I'm pretty sure this is due to the fact that the threads from the ajax calls can take any given time to complete while the for loop has finished its iterations way before, thus having only the las name in the list for display.
How can I keep the calls async while also having the names synched to the response of each call?
Thanks.
function isAlive(service) {
var buildIsAliveBox = function (isAlive, name, xhr) {
var isAliveOuterCnt = $('#isAliveOuterCont' + service);
var applyClass = isAlive ? 'alive' : 'dead';
var status = xhr.status == 0 ? 'Failed' : xhr.status;
var xhrMessage = handleHttpResponse(xhr);
var isAliveBox = $('<div class="' + applyClass + ' isAliveBox" class="btn btn-secondary" data-toggle="tooltip" data-placement="bottom" title="' + xhrMessage + '"><p class="svrName">' + name + '</br>' + status + '</p></div>');
isAliveOuterCnt.append(isAliveBox);
};
var svce = service.toLowerCase();
for (var i = 0; i < environments.qa[svce].healthUrls.length; i++) {
var data = environments[envSwitch][svce].healthUrls[i];
var name = data.split(',')[0];
var url = data.split(',')[1];
$.ajax({
url: url,
type: 'GET',
async: false,
headers: {
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json',
},
success: function (data, status, xhr) {
buildIsAliveBox(true, name, xhr);
},
fail: function (xhr, textStatus, errorThrown) {
buildIsAliveBox(false, name, xhr);
},
error: function (xhr, status, err) {
buildIsAliveBox(false, name, xhr);
}
});
}
}

Change var to let, at least for name (but ideally for the other variables as well to avoid stumbling upon this issue again later on).
var is function-scoped and not block-scoped and hence the same data, name and url variables are reused every iteration. And the response from the AJAX call comes back long after your loop finished running, so by that time those variables will have the values they last got assigned (in the last iteration of the loop).
With let you get a new scope for every iteration so each AJAX call will get a different name variable etc. it accesses in the callback.

Related

how to get the values passed into an ajax post

Here is my codes,
function getMods(name){
var init;
$.ajax({
type: "POST",
url: "init.php",
data: { 'id': getID(), 'Name': name },
cache: false,
success: function(data)
{
return data;
},
async:false
});
}
function init(){
var gm = '<br/>';
var gm1 = getMods('Name');
$('#brand').append(gm1);
var gm2 = getMods('Owner');
var gm3 = getMods('Admin1');
var gm4 = getMods('Admin2');
var gm5 = getMods('Admin3');
var gm6 = getMods('Admin4');
$('#online3').append(gm2 + gm + gm3 + gm + gm4 + gm + gm5 + gm + gm6 + gm);
}
The getMods() returned "undefined". When i use this code:
alert(data);
The page alerted the correct values.
Here is the php file init.php:
<?php
include('dbconnect.php');
if(isset($_POST['id']) && isset($_POST['Name'])){
$id = $_POST['id'];
$name = $_POST['Name'];
$init = chatMods($name,$id,$username,$password);
echo $init;
}
?>
And I add the async in the getMods().
You should make the distinction between a return value of a function (getMods() in your case) and a callback function (success).
In your code, you call getMods() which in fact doesn't return any value, hence you get undefined.
The success callback happens only after a while, after your function had already returned.
One possible solution for you (though not the greatest because it locks your JS processing) is to use an Ajax call in a synchronous way. A call that only returns when an answer from the server has been received. Use async: false in your $.ajax call, capture your return value and then return it. See this example: https://stackoverflow.com/a/2592780/290343
You should be aware, of course, that your Ajax calls might fail and you need to take that into account. Also, your code might run really slow because it does 6 remote calls to a server.

$.ajax in a for loop pulling from a php file

I have a key_gen.php file that contains a function to generate a random key. When executed, the php file gives back one key (tested and it works).
In my javascript file I have a click event on a button (that works), something like this:
$('#kg_btn').click(function(){});
Then, inside my click event I have a functions:
var get_key = function(){
for(var i = 0; i < kg_quantity; i++) {
$.ajax ({
url: "../keygen/gen_rkey.php",
type: "GET",
datatype: "json",
success: function(data) {
var j_obj = JSON.parse(data);
//alert("Success!");
prs = j_obj.test;
console.log(prs);
//add_key();
},
error: function(error) {
alert("Error! Can't send the data");
}
}); //ajax call ends
}
}
When I run this function once (by setting up the "Kg_quantity" variable to 1), every time I click my button I get a correct behavior. The result is a different key on the console.log per click.
If I set up the "kg_quantity" to any other number than 1 (for example: 3,9,10), I do get the console.log messages back, but the number generated is the same.
I was hoping that by inserting the ajax object into a for-loop would execute the ajax call several times. I tried to put the ajax call within a prototype function, as well, but I get the same result.
Edit: I tried adding a closure (as Ross suggested), but I get the exact same result.
Thanks.
AJAX is asynchronous. Your loop will finish before the first AJAX response most likely.
I would restructure that so the success response from the AJAX call will trigger the next iteration. Or if you're lazy you can just set async:false:
$.ajax ({
url: "../keygen/gen_rkey.php",
type: "GET",
async: false,
....
Or even better, put the strain on the server and reduce the back-and-forth so you get all your keys in one response:
url: "../keygen/gen_rkey.php?qty=" + kg_quantity,
UPDATE: Async design method:
function getKeys(max,cur) {
cur = cur || 1;
if (cur == max) {
return;
}
$.ajax({
....
success(function(data) {
// do stuff....
// recursive iteration
getKeys(max,cur + 1);
}
});
}
getKeys(5);

jQuery -- Variable string printed as one word

I am writing some jquery that will grab an array of xml files. It then will loop through these and parse through them to show them on the page. I have this working but one thing I would like to do if before it lists out the contents, I want it to print the name of the file. Here is the code I have.
$.get('inc/getMenuFiles.php', function(data) {
var catSplit = data.split(",");
var menuitems = $('.menuitems');
menuitems.empty();
for (i=2; i<catSplit.length; i++) {
url = "inc/menulists/" +catSplit[i].replace(/"/g, '').replace(/]/g, '');
catName = catSplit[i].replace(/"/g, '').replace(/.xml/g, '').replace(/]/g, '');
menuitems.append(catName);
$.ajax({
type: "GET",
url: url,
dataType: "xml",
success: function(xml) {
$(xml).find('item').each(function(){
var name = $(this).find('name').text();
var price = $(this).find('price').text();
menuitems.append(name + " - $" +price + "<br />");
});
}
});
}
});
This is not valid but if I do this it works
$.ajax({
type: "GET",
url: url,
async: true,
dataType: "xml",
success: function(xml) {
$(xml).find('item').each(function(){
var name = $(this).find('name').text();
var price = $(this).find('price').text();
menuitems.append(name + " - $" +price + "<br />");
});
}
}).delay();
So I guess I just need to properly add a delay, after that ajax call
This is happening because your Ajax callbacks won't execute until the current execution path finishes. To get around this you can wait until inside your ajax callback to append catName. This should work:
$.get('inc/getMenuFiles.php', function(data) {
var catSplit = data.split(",");
var menuitems = $('.menuitems');
menuitems.empty();
for (i=2; i<catSplit.length; i++) {
url = "inc/menulists/" +catSplit[i].replace(/"/g, '').replace(/]/g, '');
catName = catSplit[i].replace(/"/g, '').replace(/.xml/g, '').replace(/]/g, '');
$.ajax({
type: "GET",
url: url,
dataType: "xml",
success: (function(catName){
return function(xml) {
menuitems.append(catName);
$(xml).find('item').each(function(){
var name = $(this).find('name').text();
var price = $(this).find('price').text();
menuitems.append(name + " - $" +price + "<br />");
});
}
})(catName)
});
}
});
You still (just like in your original code) have no guarantee on the order your requests could complete, so sometimes appetizers.xml and beer.xml will load in the reverse order.
This is happening because your for-loop is faster than the AJAX call. Remember that the first A in AJAX stands fro asynchronous. Picture this...
You're at the beginning of your for-loop. You take take the first catName, and append it to menuitems. Then you fire up an AJAX call to fetch the name and price of each item in the catName. Meanwhile, the AJAX request hasn't responded yet, so we move one to the second catName, fire up another AJAX request. The first one isn't done, and we just fired a second request; so now we have 2 AJAX responses pending. In the meantime, we move on the third catName.
And so on, and so forth...
Solutions
Set the async property in your inner AJAX call to false. This will make the for-loop pause until the request is completed.
** PREFERRED ** Make your AJAX calls before the for-loop, store the response somewhere (in an array perhaps), than run your for-loop using this array.

Ajax sent on "keyup" duplicates results when fast typing!

This is my Ajax:
$("form[0] :text").live("keyup", function(event) {
event.preventDefault();
$('.result').remove();
var serchval = $("form[0] :text").val();
if(serchval){
$.ajax({
type: "POST",
url: "<?= site_url('pages/ajax_search') ?>",
data: {company : serchval},
success: function(data) {
var results = (JSON.parse(data));
console.log(results);
if(results[0]){
$.each(results, function(index) {
console.log(results[index].name);
$("#sresults").append("<div class='result'>" + results[index].name + "</div>");
});
}
else {
$("#sresults").append("<div class='result'>לא נמצאו חברות</div>");
}
}
});
}
});
When I type slowly (slower then a letter per second) I get the results correct, when I type faster I get 2 times the same results
example:
slow typing: res1 res2 res3
fast typing: res1 res2 res3 res1 res2 res3
Also, any advice on improving the code would be welcome!
Thats what is happening (pseudocode):
When you're typing slow:
.keyup1
.remove1
//asynchronous ajax1 request takes some time here...
.append1
.keyup2
.remove2
//asynchronous ajax2 request takes some time here...
.append2
When you're typing fast:
.keyup1
.remove1
//asynchronous ajax1 request takes some time here...
//and keyup2 happens before ajax1 is complete
.keyup2
.remove2
.append1
//asynchronous ajax2 request takes some time here...
.append2
//two results were appended _in a row_ - therefore duplicates
To solve duplicates problem, you would want to make your results removing/appending an atomic operation - using .replaceWith.
Build results HTML block first as string and then do the .replaceWith instead of .remove/.append:
var result = '';
for (i in results) {
result += "<div class='result'>" + results[i].name + "</div>";
}
$("#sresults").replaceWith('<div id="sresults">' + result + '</div>');
Another problem (not related to duplicates) may be that older result overwrites newer which arrived earlier (because AJAX is asynchronous and server may issue responses not in the same order it receives requests).
One approach to avoid this is attaching roundtrip marker (kind of "serial number") to each request, and checking it in response:
//this is global counter, you should initialize it on page load, global scope
//it contains latest request "serial number"
var latestRequestNumber = 0;
$.ajax({
type: "POST",
url: "<?= site_url('pages/ajax_search') ?>",
//now we're incrementing latestRequestNumber and sending it along with request
data: {company : serchval, requestNumber: ++latestRequestNumber},
success: function(data) {
var results = (JSON.parse(data));
//server should've put "serial number" from our request to the response (see PHP example below)
//if response is not latest (i.e. other requests were issued already) - drop it
if (results.requestNumber < latestRequestNumber) return;
// ... otherwise, display results from this response ...
}
});
On server side:
function ajax_search() {
$response = array();
//... fill your response with searh results here ...
//and copy request "serial number" into it
$response['requestNumber'] = $_REQUEST['requestNumber'];
echo json_encode($response);
}
Another approach would be to make .ajax() requests synchronous, setting async option to false. However this may temporarily lock the browser while request is active (see docs)
And also you should definitely introduce timeout as algiecas suggests to reduce load on server (this is third issue, not related to duplicates nor to request/response order).
You should involve some timeout before calling ajax. Something like this should work:
var timeoutID;
$("form[0] :text").live("keyup", function(event) {
clearTimeout(timeoutID);
timeoutID = setTimeout(function()
{
$('.result').remove();
var serchval = $("form[0] :text").val();
if(serchval){
$.ajax({
type: "POST",
url: "<?= site_url('pages/ajax_search') ?>",
data: {company : serchval},
success: function(data) {
var results = (JSON.parse(data));
console.log(results);
for (i in results)
{
console.log(results[i].id);
$("#sresults").append("<div class='result'>" + results[i].name + "</div>");
}
}
});
}
}, 1000); //timeout in miliseconds
});
I hope this helps.

jQuery.ajax() sequential calls

Hey. I need some help with jQuery Ajax calls. In javascript I have to generste ajax calls to the controller, which retrieves a value from the model. I am then checking the value that is returned and making further ajax calls if necessary, say if the value reaches a particular threshold I can stop the ajax calls.
This requires ajax calls that need to be processes one after the other. I tried using async:false, but it freezes up the browser and any jQuery changes i make at the frontend are not reflected. Is there any way around this??
Thanks in advance.
You should make the next ajax call after the first one has finished like this for example:
function getResult(value) {
$.ajax({
url: 'server/url',
data: { value: value },
success: function(data) {
getResult(data.newValue);
}
});
}
I used array of steps and callback function to continue executing where async started. Works perfect for me.
var tasks = [];
for(i=0;i<20;i++){
tasks.push(i); //can be replaced with list of steps, url and so on
}
var current = 0;
function doAjax(callback) {
//check to make sure there are more requests to make
if (current < tasks.length -1 ) {
var uploadURL ="http://localhost/someSequentialToDo";
//and
var myData = tasks[current];
current++;
//make the AJAX request with the given data
$.ajax({
type: 'GET',
url : uploadURL,
data: {index: current},
dataType : 'json',
success : function (serverResponse) {
doAjax(callback);
}
});
}
else
{
callback();
console.log("this is end");
}
}
function sth(){
var datum = Date();
doAjax( function(){
console.log(datum); //displays time when ajax started
console.log(Date()); //when ajax finished
});
}
console.log("start");
sth();
In the success callback function, just make another $.ajax request if necessary. (Setting async: false causes the browser to run the request as the same thread as everything else; that's why it freezes up.)
Use a callback function, there are two: success and error.
From the jQuery ajax page:
$.ajax({
url: "test.html",
context: document.body,
success: function(){
// Do processing, call function for next ajax
}
});
A (very) simplified example:
function doAjax() {
// get url and parameters
var myurl = /* somethingsomething */;
$.ajax({
url: myurl,
context: document.body,
success: function(data){
if(data < threshold) {
doAjax();
}
}
});
}
Try using $.when() (available since 1.5) you can have a single callback that triggers once all calls are made, its cleaner and much more elegant. It ends up looking something like this:
$.when($.ajax("/page1.php"), $.ajax("/page2.php")).done(function(a1, a2){
// a1 and a2 are arguments resolved for the page1 and page2 ajax requests, respectively
var jqXHR = a1[2]; /* arguments are [ "success", statusText, jqXHR ] */
alert( jqXHR.responseText )
});

Resources