How does JavaScript/jQuery invoke callback functions on multiple ajax responses - javascript

I know that the a in ajax means asynchronous, and as such there is no guarantee which order my responses will come back in, in fact its reasonable to expect the largest payload to return last.
My question however is regarding the callbacks. Sometimes when I notice my responses come back in a different order to which they were sent, the 'wrong' callback is invoked.
Take the function below.
There are some values (rangesize, dropskus, limit) (commented below) which are unique to each call, and defined in the success callback for each. When I run the script and send 5 calls, if they do come back in a different order to which they were sent. Let's say the largest response was the 2nd call to be made, that response comes back last and also invokes the last callback function.
So my question:
Does or should JavaScript / jQuery know which callback function to invoke when returning multiple responses?
Or would it be better for me to use synchronous calls?
Other notes:
When debugging in chrome, I noticed that the console logs of the responses say filename.js:linenumber. Whereas previously when I've used multiple ajax calls the console logs say VM12*:linenumber. I don't know if this has anything to do with the issue I'm facing, but I did notice that when this was the case the correct callbacks were always invoked.
function generateReview(){
var subcategories = subcatstring.split(",");
$("#rangereviewtable").html("");
$("#rangereviewtable").append(thead);
var i = 0;
var iterations = subcategories.length;
$.each(subcategories, function(key, value) {
var postdata = {group: group,
class: rrclass,
category: category,
subcategory: value,
period: period,
periodval: periodval,
stores: storesarray};
console.log(postdata);
$.ajax({
url: "ajaxrangereview.php",
type: "post",
dataType: 'json',
data: postdata,
success: function (response) {
//VALUES UNIQUE TO EACH CALL
var rangesize = parseInt($("#rangesize" + i).text());
console.log("range size: " + rangesize);
var dropskus = parseInt($("#dropskus" + i).text());
console.log("dropskus: " + dropskus);
var limit = rangesize - dropskus;
console.log("limit: " + limit);
console.log(response);
var rrtable = "";
$.each(response, function(i, item) {
rrtable += "<tr>";
rrtable += "<td class='rangereviewtext'>" + item.category + "</td>";
rrtable += "<td class='rangereviewtext'>" + item.subcategory + "</td>";
rrtable += "<td class='rangereviewtext'>" + item.brand + "</td>";
rrtable += "<td class='rangereviewtext'>" + item.sku + " - " + item.product + "</td>";
rrtable += "<td class='rangereviewnumber'>" + item.py3.toLocaleString("en") + "</td>";
rrtable += "<td class='rangereviewnumber'>" + item.py2.toLocaleString("en") + "</td>";
rrtable += "<td class='rangereviewnumber'>" + item.py1.toLocaleString("en") + "</td>";
rrtable += "<td class='rangereviewnumber'>" + item.average.toLocaleString("en") + "</td>";
rrtable += "<td class='rangereviewnumber'>" + item.sales.toLocaleString("en") + "</td>";
rrtable += "<td class='rangereviewnumber'>" + item.share + "%</td>";
rrtable += "<td>&#x2714</td>";
if(limit >= item.idnum){
rrtable += "<td>&#x2714</td>";
rrtable += "<td class='checkboxcell' onClick=\"toggleCheckMark(this, '" + item.brand + "')\">&#x2714</td>";
} else {
rrtable += "<td></td>";
rrtable += "<td class='checkboxcell' onClick=\"toggleCheckMark(this, '" + item.brand + "')\"></td>";
}
rrtable += "</tr>";
});
// increment iterations
i += 1;
$("#rangereviewtable").append(rrtable);
if(i == iterations){
var headimage = "<img src='http://url.com/images/oimage.png' width='63.5px' height='76px'>";
var table = $("#rangereviewtable").DataTable({
buttons: [{extend: 'excelHtml5', title: 'Range Review', text: 'Export to Excel'},
{extend: 'print', title: 'Range Review', message: headimage}],
});
//insert export to excel button into #rrbuttons div above actual table.
table.buttons().container().appendTo( $('#rrbuttons') );
$("#rangereviewpanel").show();
$("#generatebutton").hide();
$("#loadbutton").hide();
$("#saveasbutton").show();
generateReviewSummary(summaryarray);
} else {
//do nothing not all iterations complete
}
},
error: function(jqXHR, textStatus, errorThrown) {
i+= 1;
console.log(textStatus, errorThrown);
}
});
});
}

In your specific case I think it will work fine (your code does something like):
var j = 0;
for (var i = 0; i < 10; i++) {
setTimeout(function () {
console.log(j);
j++
}, 1000)
}
This code will outputs numbers 0..9;
But let see another example:
for (var i = 0; i < 10; i++) {
setTimeout(function () {
console.log(i);
}, 1000)
}
It outputs 9..9
So it is not safe approach to call async functions inside synchronous loop. You can use async.each (https://github.com/caolan/async#each) or promises to handle it.

JavaScript / jQuery knows exactly what callback to invoke, and is doing so correctly.
You need to learn more about JavaScript closures and scope.
I'm not sure what you mean by 'wrong' and 'right' callback with respect to your logic. If you want the special-logic to kick-in when you receive the response to the last request, then perhaps you could try replacing your use of i with references to key instead (or if I'm reading your logic correctly, you might want to use key + 1)

Related

Invoking a function multiple times but losing past data

Simply working on a homework assignment, at the end, just need to display this method 5 times. Issue is as title states once I invoke the function the next invoke overwrites past invoke.
Heres what I am currently doing.
<p id="table">
<script type="text/javascript">
showResults(race[0], name1, party1, votes1);
showResults(race[1], name2, party2, votes2);
showResults(race[2], name3, party3, votes3);
showResults(race[3], name4, party4, votes4);
showResults(race[4], name5, party5, votes5);
</script>
</p>
I have used a debugger to try and find a fix, browsed the internet for about an hour now, tried to use a .call but couldn't quite get that working either and I know document.write isn't a viable option because it rewrites everything.
Any help or useful links on the issue would be really appreciated!
Here is the showresults function
function showResults(race, name, party, votes)
{
var totalV = totalVotes(votes);
var result = "";
result += "<h2>" + race + "</h2>";
result += "<table cellspacing = '0'>";
result += "<tr>";
result += "<th>Candidate</th>"
result += "<th class='num'>Votes</th>";
result += "<th class='num'>%</th>";
result += "</tr>";
for (var i = 0; i < name.length; i++)
{
result += "<tr>";
result += "<td>" + name[i] + '(' + party[i] + ')' + "</td>";
result += "<td class='num'>" + votes[i] + "</td>";
var percent = calcPercent(votes[i], totalV)
result += "<td class='num'>(" + percent + "%)</td>";
result += createBar(party[i], percent);
result += "</tr>";
}
result += "</table>";
document.getElementById("table").innerHTML = result;
}
The Statement
document.getElementById("table").innerHTML = result;
Sets the innerHTML of your "table" element, thus overwriting it each time you set it.
try to
append(result);
to a existing DOM node so your result gets appended to the document.

Adding button with onclick event in .append() with JQuery/JavaScript

I want to add delete button to every row where status is ORDERED. I have tried different variations. This solution could work without error "Cannot read property 'id' of undefined" even though data[i] is not undefined. Alert before .click() gives right id. What is wrong here? Can I do something differently or better here?
).done( (data, status, jqXHR) => {
if(data[0].order_username != undefined) {
$("#orders_table").empty();
for(var i = 0; i < data.length; i++) {
var button = "";
if(data[i].status == "ORDERED") {
button = "<td><button class='delete'>Delete</button></td>";
}
$("#orders_table").append(
"<tr>" +
"<td>" + data[i].order_username + "</td>"+
"<td>" + data[i].work_description + "</td>" +
"<td>" + dateFormatter(data[i].orderdate) + "</td>" +
"<td>" + dateFormatter(data[i].startdate) + "</td>" +
"<td>" + dateFormatter(data[i].readydate) + "</td>" +
"<td>" + dateFormatter(data[i].accepteddate) + "</td>" +
"<td>" + dateFormatter(data[i].denieddate) + "</td>" +
"<td>" + data[i].comment_of_work + "</td>" +
"<td>" + data[i].hours + "</td>" +
"<td>" + data[i].approx_budget + "</td>" +
"<td>" + data[i].status + "</td>" +
button +
"</tr>"
)
alert(data[i].id);
$(".delete").click(() => {
deleteUser(data[i].id);
})
}
}
function deleteUser(key) {
$.ajax(
{
url: "http:localhost:3001/workorders_delete/"+key,
method: 'delete'
}).done( (data, status, jqXHR) => {
}).fail( (jqXHR, status, errorThrown) => {
console.log("Call failed: "+errorThrown);
});
}
You can set unique data attributes to the delete buttons using the the ids from the response data as follows:-
if(data[i].status == "ORDERED") {
button = "<td><button class='delete' data-deleteId =
"+data[i].id+">Delete</button></td>";
}
and then you can write a single click event listener for the delete buttons as follows:-
$(".delete").click(function(){
var id = $(this).data("deleteId");
deleteUser(id);
});
In your code :-
$(".delete").click(() => {
deleteUser(data[i].id);
});
the function deleteUser() is being called or executed on click and at the point data[i].id will not be defined.
Why is data[i] undefined?
In the loop, i is a reference to the same variable that has the value data.length after the loop.
As expected, data[data.length] is undefined. Check this with alert(i) inside .click().
$(".delete").last().click(() => {
alert(i);
deleteUser(data[i].id);
})
A simplified example:
(function wrong() {
var array = [];
for (i = 0; i < 2; i++) {
array.push(() => {
console.log(i);
});
}
array.forEach(click => click());
})();
The solution is to create a new const in the loop:
(function right() {
var array = [];
for (i = 0; i < 2; i++) {
const constI = i;
array.push(() => {
console.log(constI);
});
}
array.forEach(click => click());
})();
In addition...
$(".delete") refers to all delete buttons that exist at the point you register .click().
You should register only on the last delete button:
const constI = i;
$(".delete").last().click(() => {
deleteUser(data[constI].id);
})

Building and resorting a multidimensional array with AJAX/Javascript/jQuery

I have a multidimensional array that is built in a jQuery AJAX call when my page loads, called sumArr.
$( document ).ready( function() {
...
$.ajax({
type: 'GET',
url: 'models/table.php',
mimeType: 'json',
success: function(data) {
var sumCount = 0;
var sumArr = [];
$( "#sum-body" ).empty();
$.each(data, function(i, data) {
sumArr.push([
data[0],
data[1],
data[2],
data[3],
data[4],
data[5],
data[6],
data[7],
data[8],
data[9]
]);
var body = "<tr class='clickable-row'>";
body += "<td>" + data[0] + "</td>";
body += "<td>" + data[1] + "</td>";
body += "<td>" + data[2] + "</td>";
body += "<td>" + data[3] + "</td>";
body += "<td>" + data[4] + "</td>";
body += "<td>" + data[5] + "</td>";
body += "<td>" + data[6] + "</td>";
body += "<td>" + data[7] + "</td>";
body += "<td>" + data[8] + "</td>";
body += "<td>" + data[9] + "</td>";
body += "</tr>";
$( body ).appendTo( $( "#sum-body" ) );
sumCount = sumCount + 1;
});
console.log(sumArr);
});
...
});
I have another function that then tries to re-sort the array. I will eventually display the array on my HTML page.
function compareCols(arr, cols) {
arr.sort(function (a, b) {
console.log("comparing " + a[cols] + ", " + b[cols]);
if (a[cols] > b[cols]) {
return 1;
}
if (a[cols] < b[cols]) {
return -1;
}
return 0;
});
}
compareCols('sumArr', 0);
console.log(sumArr);
When my page loads, I get the following error:
Uncaught TypeError: arr.sort is not a function
This is baffling, because I have a much simpler version of this code as an example that works fine. See below:
var items = [
['Edward', 21],
['Sharpe', 37 ],
['And', 45 ],
['The', -12 ],
['Magnetic', 0 ],
['Zeros', 37 ]
];
function compareCols(arr, cols) {
arr.sort(function (a, b) {
console.log("comparing " + a[cols] + ", " + b[cols]);
if (a[cols] > b[cols]) {
return 1;
}
if (a[cols] < b[cols]) {
return -1;
}
return 0;
});
}
compareCols(items, 0);
console.log(items);
I can't seem to find where this code is going wrong. Can anyone spot where the error is? I've combed through the code and can't find anything. I'm guessing it has something to do with AJAX, but don't know for sure. I originally had my array as an object, but changed it to an array or arrays.
Two issues here:
When calling compareCols('sumArr', 0); should remove the quotes as suggest by kurt in the comments.
But the bigger problem is that the sumArr may not be defined as your global variable.
You need to call the compareCols only after a successful ajax call.
Make sure you remove the var in the ajax section so the sumArr = [] refers to the global variable.

Sort HTML Table, that gets appended via JQuery

Hello i'm trying to use Tablesorter(https://github.com/christianbach/tablesorter) to sort a table of mine which i generate throu JQuery.appends. This is how my code looks:
$(document).ready(function() {
*Lotsa more code .....*
$.get("../skillqueue",{keyid: keyid, charid: charid},function(xmlskillqueue){
console.log("XML Skillqueue");
console.log(xmlskillqueue);
//Variables for
var rowsets = xmlskillqueue.getElementsByTagName("rowset");
var skillrows;
for(var i = 0; i < rowsets.length; i++){
if(rowsets[i].getAttribute("name") == "skillqueue"){
skillrows = rowsets[i].getElementsByTagName("row");
}
}
//Defines Table Headers
$("#tableskillqueuelist").append(
"<thead>" +
"<tr>" +
"<th>Order: </th> "+
"<th>Skill Name: </th> "+
"<th>Training to: </th> "+
"<th>Starts:</th> "+
"<th>Ends:</th> "+
"</tr> "+
"</thead>"+
"<tbody>"
);
for(var i = 0; i < skillrows.length; i++){
(function(i, skillrows) {
$.get("../getitemname", {itemid:skillrows.getAttribute("typeID")},function(itemname){
$("#tableskillqueuelist").append(
"<tr> " +
"<td>" + skillrows.getAttribute("queuePosition") + ". " +
"<td>" + itemname + "</td>" +
"<td>" + "|Train to: " + skillrows.getAttribute("level") + "</td>" +
"<td>" + "|Training Starts: " + skillrows.getAttribute("startTime") + "</td>" +
"<td>" + "|Training Ends: " + skillrows.getAttribute("endTime") + "<td>" +
"</tr>"
);
})
})(i, skillrows[i]);
}
//Ends the table body
$("#tableskillqueuelist").append("</tbody>");
});
});
Now i'm wondering what i need to do to have it successfully run the $("#tableskillqueuelist").tablesorter(); method. Since it seems like whenever i try and run it, the #tableskillqueuelist seems to be empty.
You need to tell table sorter that you've changed the data and that you want to sort it by triggering events.
Example from the docs: http://tablesorter.com/docs/example-ajax.html
$("table").tablesorter();
$("#ajax-append").click(function() {
$.get("assets/ajax-content.html", function(html) {
// append the "ajax'd" data to the table body
$("table tbody").append(html);
// let the plugin know that we made a update
$("table").trigger("update");
// set sorting column and direction, this will sort on the first and third column
var sorting = [[2,1],[0,0]];
// sort on the first column
$("table").trigger("sorton",[sorting]);
});
return false;
});
HTH

AJAX, response of type callbackvar or callbackfunction

For a homework assignment, we are using a GET method to access to the web server where it returns types of JSON, XML, and script. For script, it's defined as:
If script is specified, you must also either specify
a. callbackfunction
callme([{"id":"1383","name":"Sweet Deal","desc":"Great place for books","url":"http://amazon.com"}]);
b. callbackvar
items=[[{"id":"1383","name":"Sweet Deal","desc":"Great place for books","url":"http://amazon.com"}]];
So my sendRequest() method looks like:
function sendRequest()
{
var transmission = document.getElementById("transmission").value;
var url = "http://classwebsite.php"
+ "?userid=crystal"
+ "&response=" + encodeValue(transmission);
if (transmission == "script") {
url += "&callbackfunction=formatData";
}
var callback = {success:handleResponse,
failure:handleFailure,
timeout:5000
};
var transaction = YAHOO.util.Connect.asyncRequest("GET", url, callback, null);
}
But I'm not really sure what that means to use callbackvar or callbackfunction in the hw instructions. Cause to me, the packet just looks like a JSON packet and I already have a method where I parse the JSON packet and format the data.
// What gets passed into this method is: YAHOO.lang.JSON.parse(response.responseText);
function formatData(message) {
var str = "<table border=1 class='editable'>";
for (var i = 0; i < message.length; i++) {
str += "<tr>" + "<td>" + message[i].id + "</td>" +
"<td>" + message[i].name + "</td>" +
"<td>" + message[i].url + "</td>" +
"<td>" + message[i].desc + "</td>" +
"<td>" + "<a href='#' onclick='deleteRequest(this); return false' id='" + message[i].id + "'>delete</a>" + "</td>" + "</tr>";
}
str += "</table>";
return str;
}
So in my handleResponse method from sendRequest, I thought I could just do this:
else if (transmission == "script") {
msg = formatData(response);
}
var responseOutput = document.getElementById("responseOutput");
responseOutput.innerHTML = msg;
Like I did with my JSON packet, but I don't see any output. Any thoughts? thanks.
the response from the server is script content. So, inject the script tag with content of the responseText from the server on the page, and you're good to go.
When you do -
var callback = {success:handleResponse,
failure:handleFailure,
timeout:5000
};
var transaction = YAHOO.util.Connect.asyncRequest("GET", url, callback, null);
The callback that is invoked is handleResponse, and not formatData.

Categories

Resources