AJAX acting in a multi threaded manner - javascript

I understand that JavaScript is single threaded (as explained in this question: If Javascript is not multithreaded, is there any reason to implement asynchronous Ajax Queuing?), however I am trying to understand how this applies to the application I have developed. Please see the code below:
function GetSQLTable() {
var str = $("#<%=fieldDatabaseReferences.ClientID%>")[0].value
var res = str.split(",");
$("#LoadingImage").show();
$("#LoadingImage2").show();
for (var i = 0; i < res.length; i++) {
(function (i, res) {
setTimeout(function () {
GetSQLTable2(i, res.length, res)
}, 0);
})(i, res);
}
}
function GetSQLTable2(i,reslength,res) {
//if (i == 0)
//{
// var start = new Date().getTime();
//}
var div = document.createElement('div');
div.id = "div" + i
document.getElementById('info_div').appendChild(div);
var PossiblesPage = false;
$.ajax({
type: "POST",
url: "PrimaryNominalAjax.aspx/GetSQLTable",
data: '{username: "' + $("#<%=fieldUserName.ClientID%>")[0].value + '", terminalname: "' + $("#<%=fieldTerminalName.ClientID%>")[0].value + '", terminalip: "' + $("#<%=fieldTerminalIP.ClientID%>")[0].value + '", mappingid: "' + res[i] + '", usergroup: "' + $("#<%=fieldUserGroup.ClientID%>")[0].value + '", usn: "' + $("#<%=fieldUSN.ClientID%>")[0].value + '", requester: "' + $("#<%=fieldRequester.ClientID%>")[0].value + '", reason: "' + $("#<%=fieldReason.ClientID%>")[0].value + '", rrd: "' + $("#<%=fieldRRD.ClientID%>")[0].value + '", review: "' + $("#<%=fieldReview.ClientID%>")[0].value + '", possibles: "' + PossiblesPage + '",linkno: "", urn1: "", urn2: ""}',
contentType: "application/json; charset=utf-8",
timeout: 80000000,
dataType: "json",
success: OnSuccess(i, reslength),
error: OnError,
failure: function (response) {
alert('there was an error loading the webpage')
}
});
}
fieldDatabaseReferences is populated on the server side. The AJAX connects to multiple local databases (up to 30) and puts the information on the screen as and when it is ready.
The calls to the various database servers are asynchronous. Surely this has a multi threaded effect?

JavaScript is single threaded. When asynchronous events occur, they are are pushed into a queue waiting to be executed until the thread is idle. Consider the following example:
var run = true;
var brk = Date.now() + 5000; // five seconds from now
setTimeout(function(){
run = false; // set the run variable to false _asynchronously_
}, 1000); // after one second
while(run && Date.now() < brk); // loop while both conditions are true
console.log("run:", run); // logs run: true (which was the initial value)
When do you suppose the loop will terminate? One second? No it would run indefinitely (if Date.now check was not there). The fact that the value logged in console is true confirms that the timeout is not fired. It is in the queue, waiting for the var run = true...console.log() block to terminate.
As for your example, the order of execution would be:
/* note: no two functions execute at same time */
GetSQLTable();
/* functions scheduled via setTimeout execute one by one */
GetSQLTable2(0, ...);
GetSQLTable2(1, ...);
GetSQLTable2(2, ...);
/* AJAX requests complete one by one, not necessarily in the order they started */
OnSuccess(2);
OnSuccess(0);
/* JavaScript thread could be idle during callbacks */
OnSuccess(1);
References:
http://ejohn.org/blog/how-javascript-timers-work/
http://blog.carbonfive.com/2013/10/27/the-javascript-event-loop-explained/

Related

Why is my code not waiting for the promises to complete?

I have the following code making multiple AJAX calls (by calling ExecuteServiceMethod) in an jquery "each" loop. I'd like to wait for all the calls to complete before calling the "__doPostBack", but it doesn't seem to be working and the postback gets called before all the ajax calls return.
Here's the calling code:
$("table[id*=ChangeAllGridView]").on("hide.bs.dropdown", ".dropdown.bootstrap-select", function (event) {
var LotID = '<%=Session[ID + "EditedLot"]%>';
var SplitsList = $(event.target).children("button.dropdown-toggle").attr("title");
var SplitsArray = $(event.target).children("select").val();
var promises = [];
$(".selectpicker[id*=SplitDropDown]").not("[id*=SplitDropDown_All]").each(function () {
$(this).selectpicker("val", SplitsArray);
var WID = $(this).attr("id").split("_")[3];
var a = ExecuteServiceMethod("LotEditorWebService.asmx", "UpdateWaferSplits", ["LotID", LotID, "WID", WID, "SplitsList", SplitsList], function () { });
promises.push(a);
});
Promise.all(promises).then(function () {
__doPostBack($(event.target).attr('id'), '');
});
});
And here's the ExecuteServerMethod routine making the AJAX call:
function ExecuteServiceMethod(page, fn, paramArray, successFn, errorFn) {
var paramList = '';
if (paramArray.length > 0) {
for (var i = 0; i < paramArray.length; i += 2) {
if (paramList.length > 0) paramList += ',';
paramList += '"' + paramArray[i] + '":"' + paramArray[i + 1] + '"';
}
}
paramList = '{' + paramList + '}';
return $.ajax({
type: "POST",
url: page + "/" + fn,
contentType: "application/json; charset=utf-8",
data: paramList,
dataType: "json",
success: successFn,
error: errorFn
});
}
I've tried a number of iterations of this, including putting a "new Promise()" statement around the AJAX call and returning that, but nothing seems to help.
Would appreciate your help on this.
Thanks.
try wrapping like this: Promise.resolve($.ajax(...))
edit: you can also try removing the success and error callbacks in the ajax function
edit edit: also try catching any errors on your Promise.all

JS code executing out of order? New to JS [duplicate]

This question already has answers here:
How do you make javascript code execute *in order*
(9 answers)
Closed 3 years ago.
First off, I am new to JS, HTML, and CSS.
When I debug this code, it seems to not run in the order I would expect, top to bottom. What am I doing wrong?
This code is supposed to use the twitch.tv api to find the channels I follow, take their ID, run another ajax call to see if they are live, and if they are live, present them on my html page.
I have tried debugging this and running ajax calls in Postman and the calls do work for the Twitch API and I get the information I want. The problem is, it runs out of order so the code doesnt do what I intend. I tried debugging with Chrome break points and Debugger for Chrome extension in VS Code.
$(document).ready(function() {
var userID = [];
var logo = [];
var status = [];
var name = [];
var totalFollowing;
//Get User IDs for following
$.ajax({
type: 'GET',
url: 'https://api.twitch.tv/kraken/users/Lucidic_/follows/channels?limit=100',
headers: {
'Client-ID': 'hidden',
'Accept': 'application/vnd.twitch.v5+json'
},
success: function(followingData) {
for (var i = 0; i < followingData._total; i++) {
totalFollowing = followingData._total;
userID.push(followingData.follows[i].channel._id);
logo.push(followingData.follows[i].channel.logo);
status.push(followingData.follows[i].channel.status);
name.push(followingData.follows[i].channel.display_name);
}
}
});
var allFollowingURL = "https://api.twitch.tv/helix/streams?";
for (var i = 0; i < totalFollowing; i++) {
allFollowingURL.concat("&user_id=" + userID[i])
}
$.ajax({
type: "GET",
url: allFollowingURL,
headers: {
'Client-ID': 'hidden',
'Accept': 'application/vnd.twitch.v5+json'
},
success: function(channelData) {
if (channelData.data.type = 'live') {
$("#followerInfo").prepend("<div class = 'row'>" + "<div class = 'col-sm-4'>" + "<img src='" + logo[i] + "'>" + "</div>" + "<div class = 'col-sm-4'>" + name[i] + "</div>" + "<div class = 'col-sm-4'>" + status[i] + "</div></div>");
}
}
});
});
The reason for the code "running out of order" is because the API requests you are making run asynchronously. These requests take time to return with the data, so instead of holding your program hostage while it waits for this request to come back, the program keeps on executing, then goes back to execute the code in the request's call back function once the data has returned.
See more on learning about something called a promise here https://developers.google.com/web/fundamentals/primers/promises. These are very powerful and useful for handling things like http requests.

Combine data from different URLs in ajax

I have one ajax request which i use to extract data from API, and create a table from the extracted data. Now i need to do the same, but to extract the data from two different URLs and merge is to the same table (retTable).
Here is my current code (one ajax request):
$.ajax(
{
url : '/url/status',
type: "GET",
success:function(data, textStatus, jqXHR)
{
theRows = extract_status_data(data)
},
error: function(jqXHR, textStatus, errorThrown)
{
alert('error')
}
});
}
function extract_status_data(jsonDataRaw){
jsonResultSect = jsonDataRaw['result']
retTable = ""
for( key in jsonResultSect){
statusParam = jsonResultSect[key]
a = statusParam['a']
b = statusParam['b']
c = statusParam['c']
d = statusParam['d']
e = statusParam['e']
retTable += "<tr><td>" + dropDownList(key) + "</td><td>" + key + "</td><td>" + a + "</td><td>" + b + "</td><td>" + c + "</td><td>" + d + "</td><td>" + e + "</td></tr>"
}
return retTable
}
How would be correct to combine the data from two different URLs? Please advise.
I can't hammer out a really robust solution right now, but here is what I came up with: https://jsfiddle.net/heejse8h/
Basically the principal is that you place all the URLs in an array and keep a flag variable incrementing for every url you pull from. This might look like this:
urls = [
'/url/status',
'/url/status2'
];
var i = 0;
Then when you execute the AJAX, you'll want to store that in some array
var result = [];
For my AJAX call in the jsfiddle, I used this basic structure
$.ajax({
url : urls[i],
type: "GET",
success: function(data) {
// simplified example of storing the results
// the example code from the fiddle is more
// involved.
result[key].push(data);
if(urls[++i] !== undefined){
// if there is another URL, use the same
// ajax object (using `this`), extend it,
// changing only the URL, and call it.
// the important part is that the `this`
// object has a reference to the currently
// executing `success` method.
$.ajax($.extend(this, {url: urls[i]}));
} else {
// otherwise, we're at the end of our URLs
// and we can focus on final formatting and
// display of the data.
for( key in result ){
$('#mytable').append("<tr><td>" + dropDownList(key) + "</td><td>" + key + "</td>" + result[key].join('') + "</tr>");
}
}
}
});
In the end I would have liked to flesh this out and use the DOM API to actually create nodes rather than constant concatenation, but this solution already diverges from the original code quite a bit. You might want to consider creating a function that parses an object rather than relies on concatenation.

Return false, not cancelling for each loop

I am using a function, showVisible(), to gather all the marker ID's from a googlemap, pass them to an array, and then call the corresponding object data from the server using AJAX.
This is triggered whenever someone click a marker cluster on the map. I need to stop loop cycling through the array if someone clicks on a marker before all the ajax calls have been made.
I have tried to use an if statement so that if breakAjax is "true" then the loop should return false. Even though I can see on the console that the the variable shifts to true, it isn't triggering the break.
The full code for the function is below:
function showVisible() {
visRunning = true
console.log('show visible')
$('#objects_list').empty()
function unique(list) {
var result = [];
$.each(list, function(i, e) {
if ($.inArray(e, result) == -1) result.push(e);
});
return result;
}
scoots = unique(locStr)
totalScoots = scoots.length
//console.log('total scoots ' + totalScoots);
var scootOutput = 0
for (var i = 0; i < scoots.length; i++) {
if (scootOutput === 0) {
$('#objects_list').empty()
}
console.log('breakAjax: ' + breakAjax)
countManager()
$.ajax({
url: 'https://scootapi.stuffstory.com/api/stuff/' + scoots[i],
dataType: 'json',
type: "get",
success: function(stuffData, textStatus, jqXHR) {
$('#objects_list').append('<a class="stuff_wrapper" target="_blank" onclick="ga("send", "event", "grid view scooter ' + stuffData.id + '", "click", "frontPage")" href="../scooter/?view=' + stuffData.id + '"><div class="stuff_holder" style="background-image:url(' + stuffData.image + '/convert?w=250&h=250);"><div class="scooter_title_sm"><div class="title_wrapper"><div class="scooter_title_1 cursive_sm">' + stuffData.make + '</div><div class="scooter_title_2 lato_sm">' + stuffData.model + '</div><div class="scooter_title_3 cursive_sm">' + stuffData.year + '</div></div></div></div></a>')
console.log('breakAjax: ' + breakAjax)
stuffSizer()
scootOutput++
if (scootOutput === totalScoots) {
visRunning = false
}
},
error: function(data, textStatus, jqXHR) {}
})
if (breakAjax === "true") {
return false;
}
};
}
The value of breakAjax does not change inside the loop.
JavaScript is single threaded.
Code that isn't inside the loop cannot change the value of that variable while the loop is running (which happens practically instantaneously anyway).
You seem to have forgotten that the A in Ajax stands for asynchronous.
You are going over the loop, making n HTTP requests and setting up n event handlers (the success function) that will run when the got HTTP response event fires.
If you want to stop the success function from doing anything with the data, then you need to test breakAjax inside the success function.
It is far too late, at that point, to stop the HTTP request being sent. If that is the goal, then you'll need to refactor your logic (probably using recursive functions) so that your loop is incremented by the success handler instead of using for.

Why is Javascript output being held back in Google Chrome?

I have javascript/jquery code which fetches info and updates it into the database with a mixture of while/for loops. While fetching, I have a div which shows a current progress log of whats going on. In Firefox, as the script is running it updates the div at the same time as it should. In Google Chrome, it runs the entire loop, holding back the log, and only outputs it until the script is finished running. Anyone have any idea why this is happening?
Here is my code:
$(document).ready(function() {
add_text("test");
var array_length = num_sets;
for(var i = 0; i < array_length; i = i + 1) {
var setId = sets[i]['id'];
var setName = sets[i]['name'];
var setLinkName = sets[i]['link'];
var setNumCards = sets[i]['num_cards'];
add_text("Beginning to fetch set \"" + setName + "\"");
add_text("Found " + setNumCards + " total cards.");
while(ii < setNumCards) {
var card_name = sets[i]['cards'][ii]['name'];
var card_link = sets[i]['cards'][ii]['link'];
add_text("Fetching card " + sets[i]['cards'][ii]['name']);
fetch_card(sets[i]['cards'][ii]['link'], setId);
}
}
});
add_text function:
function add_text(text) {
$("#status_text").append("<br />" + text);
}
fetch_card function:
function fetch_card(card_link, set_id)
{
$.ajax({
url: "fetch_card.php?link=" + card_link + "&set_id=" + set_id,
context: document.body,
async: false,
success: function(){
ii = ii + 1;
}
});
}
You are using synchronous ajax calls (which are generally not very desirable). The browser can block all activity until that ajax call completes. Whether or not the browser updates the screen during a synchronous ajax call is up to the browser.
Your code would be much better if it was rewritten to use asychronous ajax only. It takes a little more work to structure your code properly to work with asynchronous ajax calls, but the browser remains completely responsive during the asynchronous ajax calls.
I'm not entirely sure how you were using the ii variable in your original implementation (as it wasn't declared or initialized in the code you included), but this is the general structure you could use. It uses the traditional for loop to collect all the data you wanted in an array, then calls the ajax function one a time on that data. It isn't clear to me how you're actually doing anything with the returned ajax info, but perhaps that just isn't something you included here:
$(document).ready(function() {
add_text("test");
var array_length = num_sets;
var fetchData = [];
var fetchIndex = 0;
for(var i = 0; i < array_length; i++) {
var setId = sets[i]['id'];
var setName = sets[i]['name'];
var setLinkName = sets[i]['link'];
var setNumCards = sets[i]['num_cards'];
add_text("Beginning to fetch set \"" + setName + "\"");
add_text("Found " + setNumCards + " total cards.");
for (var ii = 0; ii < setNumCards; ii++) {
var card_name = sets[i]['cards'][ii]['name'];
var card_link = sets[i]['cards'][ii]['link'];
add_text("Fetching card " + sets[i]['cards'][ii]['name']);
fetchData.push({link: sets[i]['cards'][ii]['link'], id: setId});
}
}
function next() {
if (fetchIndex < fetchData.length) {
fetch_card(fetchData[fetchIndex].link, fetchData[fetchIndex].id, next);
fetchIndex++;
}
}
function fetch_card(card_link, set_id, successFn) {
$.ajax({
url: "fetch_card.php?link=" + card_link + "&set_id=" + set_id,
context: document.body,
async: true,
success: successFn
});
}
next();
});

Categories