Why is Javascript output being held back in Google Chrome? - javascript

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

Related

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.

javascript execute nested ajax call inside a for loop

So, I have two code snippets - both of them are executing without errors. Both are meant to do the same thing - loop through a list of words and look up documents these words appear in (SOLR), then pull paragraphs where these words appear from each document. However they don't return the desired result. The first one skips over the nested ajax call (the one inside the for each loop) and if runs the ajax call at all, it's after the main (parent) loop already finished. The second one only works properly if there is an alert there. If the user acknowledges the alert, then the second loop executes correctly. Without the alert, the second loop gets skipped. Can anyone shed some light at what is wrong here. I've tried ajax {complete:...} and ajax{}.done but they didn't work either.
Here is the first snippet:
for (var m = 0; m < definitions.length; m++ ) {
var url = pathtomyfile;
var doctitle, docname, docs;
var htmlBody, pHtml, fullHTML;
$.ajax({
url: url,
async: false,
dataType: 'json',
success: function (result){
docs = result.response.docs;
},
complete: function () {
$(docs).each (function (){
var doctitle = this.dc_title_str.toString().replace (/(\.html|\.htm)/, '');
var docname = filePathToUrl (this.resourcename.toString ());
var url = decodeURIComponent (docname);
$.ajax({
url: url,
async: false,
dataType: 'html',
success: function (resp){
fullHTML = resp.toString();
htmlBody = fullHTML.split (/<body>/)[1].split (/<\/body>/)[0];
htmlBody = htmlBody.replace (/\s/g, " ").replace (/ /g, " ").replace (/(<a id="x\d+?")\/>/g, "$1></a>");
var pHtml = $(htmlBody).find("#id").parent();
pHtml = $(pHtml).wrap('<p/>').parent().html();
pHtml = pHtml.replace (/\<a id/g, "#s#").replace (/<\/a>/g, "#e#").replace (new RegExp ("\\b(" + en_word.replace (/\s/g, "(<[^<]+?>| )*")+"(s|ed|ing|es)*)\\b", "ig"), "<span style='background-color:yellow'>$1</span>").replace (/#s#/g, "<a id").replace (/#e#/g, "<\/a>");
hsnip += "<p><b><i>From " + doctitle + ":</i></b></p> " + pHtml;
}
});
})
}
});
}
this is the second snippet:
for (var m = 0; m < definitions.length; m++ ) {
var url = pathtomyfile;
var doctitle, docname, docs;
var htmlBody, pHtml, fullHTML;
$.ajax({
url: url,
async: false,
dataType: 'json',
success: function (result){
docs = result.response.docs;
}
});
alert ('ok');
for (var b = 0; b < docs.length; b ++) {
doctitle = docs[b].dc_title_str.toString().replace (/(\.html|\.htm)/, '');
docname = filePathToUrl (docs[b].resourcename.toString ());
var rawFile = new XMLHttpRequest ();
docname = decodeURIComponent (docname);
rawFile.open ("GET", docname, false);
rawFile.onreadystatechange = function () {
if (rawFile.readyState === 4 && (rawFile.status === 200 || rawFile.status === 0)) {
fullHTML = rawFile.responseText.toString();
htmlBody = fullHTML.split (/<body>/)[1].split (/<\/body>/)[0];
var pHtml = $(htmlBody).find("#id").parent();
pHtml = $(pHtml).wrap('<p/>').parent().html();
hsnip += "<p><b><i>From " + doctitle + ":</i></b></p> " + pHtml;
}
}
rawFile.send (null);
}
}
This sounds like an async problem, is not the best idea to make AJAX calls in a loop, you can replicate a loop with recursivity though, have the function only make one call, and call the function again when each call is finished, and add a breakpoint with an if that checks if you're finished.

Ajax request in for loop and order of indices

I have a problem concerning the execution of ajax requests in a for loop. I already searched the web for it and found some solutions which I already implemented to avoid running the request synchronously. Unfortunately these solutions don't provide information how to ensure, that the success block gets called in the correct order aswell.
This is my code:
for (var i = 0; i < array.length; i++) {
(function(index) {
var path = array[index].split(";")[1];
var selectedRevision = array[index].split(";")[0];
$.ajax({
url: 'svntojson.jsp?revHistoryForPath=' + path,
dataType:'text',
success:function(data){
console.log(index);
var $li = $("<li/>").text(path).addClass("ui-corner-all")
.prepend("<div class='handle'><span class='ui-icon ui-icon-carat-2-n-s'></span></div>")
.append('<button class="delete"></button>')
.append('<select class="revHistoryOptions" style="float:right;margin-right:5px;">' + data.trim() + '</select>');
$("#list").append($li);
$("#list").sortable('refresh');
$('.revHistoryOptions').eq(index).children('option[value=' + selectedRevision + ']').attr('selected', 'selected');
}
});
})(i);
}
However the order of indices can change because the one ajax request succeeds earlier. This wouldn't be a problem but I am appending some list elements in the success block and I need the exact order.
So my question is how to ensure that the success block of my ajax request will be called in the order of the for loop indices from 0 to n-1.
WRONG DESIGN : If you want a complete synchronized behavior and not involving any user interaction between iterations, you can avoid
looping and use single ajax request.
You can use a single ajax when you want it completely synchronized:
$.ajax({
url: 'svntojson.jsp?inputArray=' + array,
dataType: 'json',//Note I changed this to json to receive the array on outputs
success: function (data) {
var resArray = data;
for (var index = 0; index < resArray.length; index++) {
var res = resArray[index];
var $li = $("<li/>").text(res.path).addClass("ui-corner-all")
.prepend("<div class='handle'><span class='ui-icon ui-icon-carat-2-n-s'></span></div>")
.append('<button class="delete"></button>')
.append('<select class="revHistoryOptions" style="float:right;margin-right:5px;">' + res.data + '</select>');
$("#list").append($li);
$("#list").sortable('refresh');
$('.revHistoryOptions').eq(index).children('option[value=' + res.selectedRevision + ']').attr('selected', 'selected');
}
}
});
on the server:
process the input array and generate output for each element of array. (Psuedocode as following):
var resArray = new Array(inputArr.length);
for (var i = 0; i < inputArr.length; i++) {
var res = new res();
res.path = inputArr[i].split(";")[1];
res.selectedRevision = inputArr[i].split(";")[0];
resArray.push(res);
}
return resArray;

Populating an object with ajax in a loop

I need to pull data from a series of .csv files off the server. I am converting the csvs into arrays and I am trying to keep them all in an object. The ajax requests are all successful, but for some reason only the data from the last request ends up in the object. Here is my code:
var populate_chart_data = function(){
"use strict";
var genders = ["Boys","Girls"];
var charts = {
WHO: ["HCFA", "IWFA", "LFA", "WFA", "WFL"],
CDC: ["BMIAGE", "HCA", "IWFA", "LFA", "SFA", "WFA", "WFL", "WFS"]
};
var fileName, fileString;
var chart_data = {};
for (var i=0; i < genders.length; i++){
for (var item in charts){
if (charts.hasOwnProperty(item)){
for (var j=0; j<charts[item].length; j++) {
fileName = genders[i] + '_' + item + '_' + charts[item][j];
fileString = pathString + fileName + '.csv';
$.ajax(fileString, {
success: function(data) {
chart_data[fileName] = csvToArray(data);
},
error: function() {
console.log("Failed to retrieve csv");
},
timeout: 300000
});
}
}
}
}
return chart_data;
};
var chart_data = populate_chart_data();
The console in Firebug shows every ajax request successful, but when I step through the loops, my chart_data object is empty until the final loop. This is my first foray into ajax. Is it a timing issue?
There are two things you need to consider here:
The AJAX calls are asynchronous, this means you callback will only be called as soon as you receive the data. Meanwhile your loop keeps going and queueing new requests.
Since you're loop is going on, the value of filename will change before your callback is executed.
So you need to do two things:
Push the requests into an array and only return when the array completes
Create a closure so your filename doesn't change
.
var chart_data = [];
var requests = [];
for (var j=0; j<charts[item].length; j++) {
fileName = genders[i] + '_' + item + '_' + charts[item][j];
fileString = pathString + fileName + '.csv';
var onSuccess = (function(filenameinclosure){ // closure for your filename
return function(data){
chart_data[filenameinclosure] = csvToArray(data);
};
})(fileName);
requests.push( // saving requests
$.ajax(fileString, {
success: onSuccess,
error: function() {
console.log("Failed to retrieve csv");
},
timeout: 300000
})
);
}
$.when.apply(undefined, requests).done(function () {
// chart_data is filled up
});
I'm surprised that any data ends up in the object. The thing about ajax is that you can't depend on ever knowing when the request will complete (or if it even will complete). Therefore any work that depends on the retrieved data must be done in the ajax callbacks. You could so something like this:
var requests = [];
var chart_data = {};
/* snip */
requests.push($.ajax(fileString, {
/* snip */
$.when.apply(undefined, requests).done(function () {
//chart_data should be full
});

How to return an array from a Javascript Facebook API request?

Hopefully there is an easy way to do this and my Javascript skills are just lacking. I am wanting to call a function that will get some Facebook posts, add them to an array and return to use elsewhere. Current code is below.
function GetFaceBookStream(name, max) {
FB.init({ apiKey: 'removed for post' });
var lastDate = '2011-04-29Z00:00:00';
var faceBookArray = [];
var faceBookString;
FB.api("/" + name + "/feed", { limit: max, since: lastDate }, function (response) {
var sb = string_buffer();
for (var i = 0; i < response.data.length; i++) {
var post = response.data[i];
sb.append("<li class='facebook'>");
sb.append("<img alt=\"Facebook\" src='Images\\Carousel\\fbIcon.png\' />");
sb.append("<h4>FACEBOOK</h4>\n");
sb.append("<div class=\"from-name\">" + post.from.name + "</div>");
sb.append("<div class=\"time\">" + post.created_time + "</div>");
if (post.message != undefined) {
sb.append("<div class=\"message\">" + post.message + "</div>");
}
sb.append("</li>stringSplitMarker");
}
faceBookString = sb.toString();
faceBookArray = faceBookString.split('stringSplitMarker');
});
return faceBookArray;
}
I realize this set up won't work due to variable scope in Javascript, but this is basically what I'm trying to achieve. Any help will be greatly appreciated!
You're making an asynchronous AJAX request.
The callback only runs after your code finishes.
You need to pass the data back using a callback.
For example:
function GetFaceBookStream(name, max, callback) {
...
FB.api(..., function(response) {
...
callback(something, else);
});
}
You can call the function by supplying a callback to receive the response:
GetFaceBookStream(name, max, function(param1, param2) {
//This code runs later and can use the response.
});

Categories