Ajax request in for loop and order of indices - javascript

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;

Related

How do an array of pages can be loaded

Instead of request each module per line of code, I'm saving all the modules' string page in an array of modules,
var modules = ['mod1.html','mod2.html', ... , 'modN.html'];
and then pass to a function that suppose to put all the loaded modules into the modules-panel div at once.
function loadModules(modules, callback){
var content = [];
for(var i = 0; i < modules.length; i++){
$('#modules-panel').load(modules[i],function(){
content.push($('#modules-panel').html());
});
}
callback();
}
The issue is that only the last module appears where it should be.
The function should save the loaded module ( page ) into a stack repeatedly and then append all the modules in the page at once.
Given that you want to get the HTML from these pages, it would be better to use $.ajax directly and then read the response from that instead of repetedly filling an extraneous element.
Also, your callback() logic is flawed. You need to call that once all the AJAX requests have completed. For that you can use $.done. Try this:
function loadModules(modules, callback){
var content = [], requests = [];
for(var i = 0; i < modules.length; i++){
requests.push($.ajax({
url: modules[i],
success: function(html) {
content.push($('#modules-panel').html());
}
}));
}
$.when.apply($, requests).done(callback);
}
It should be noted that this may not be the best pattern to use as it will mean your server may be deluged with requests. If you can, I would look in to using server-side includes instead.
This is the place where the loadModules function is called:
var modules = ['loadfile.html', 'getFBinfo.html'];
loadModules(modules, function(data){
var total = '';
for(var i = 0; i < data.length; i++){
total += data[i];
}
$('#modules-panel').html(total);
});
Thanks for clarifying the issue! However the answer I've got was pretty simple!
I've taken the suggestion about ajax. The function became this:
function loadModules(modules, callback){
var content = [];
for(var i = 0; i < modules.length; i++){
$.ajax({
url: modules[i],
success: function(html) {
content.push(html);
if(content.length == modules.length){
callback(content);
}
}
});
}
}
As can be seen, I could return the callback function by comparing the length between what is passed to the function and the total possible/real length (the modules' length).
The problem now is to know what happens if the ajax request gets an error from the server.

in javascript for loop is executing before the get method executes

In the following example variable e contains all the selected clients from a select box, I am iterating them one by one in a for loop and passing them via jQuery's get method to take values according to client but the for loop is executing before get method ends and due to that it changes the value of val (which is next value). How to resolve this problem ?
var e = document.getElementById("client");
for (var i = 0; i < e.options.length; i++) {
if (e.options[i].selected) {
var val = e.options[i].value;
alert(val); // here it is coming normally
$('#fund').append('<option value=' + select.options.length + '>---' + val + '----</option>');
$.get("listFundsForClient", { client: val }, function(data) {
alert("2nd:" + val);// here it is taking next value due to for loop iteration
});
}
}
it is because here val is a closure variable, it can be re-written as
$('#client option:selected').each(function(){
var $this = $(this), val =$this.val();
alert(val); // here it is coming normally
$('#fund').append('<option value='+select.options.length+'>---'+val+'----</option>');
$.get("listFundsForClient", {client: val}, function(data) {
alert("2nd:"+val);// here it is taking next value due to for loop iteration
});
})
You can make the call using $.ajax() to do it synchronously, like this:
$.ajax({
url: myUrl,
async: false,
data: myData,
success: function(data) {
//stuff
}
});

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

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

mulitple JSON feeds in an array

I am trying to pull 5 separate JSON feeds and have it looping where every individual has a nested getJSON.
var feedList [feed1,feed2,feed3,feed4,feed5];
for (feed in feedList) {
var index = 0;
$.getJSON(feedList[feed], function(data) {
$.each(data.items, function(i, obj) {
$('li').append(obj.title + '=' + index + '<br>');
});
index++;
}
}
For some reason in firebug it shows that I go through the for in loop then pull the feed successfully then completely bypass the anything inside the .getJSON loop. It doesn't go back into .getJSON loop until all the feeds are pulled. This messes up the order of the items being pulled and also the order is random every time I refresh the page. (e.g. feed2 is listed first then feed4 next)
I've also tried doing a regular for loop instead of a for in loop but it still changes nothing.
Can anyone help me?
Send the next request on success of the previous one by using a recursive function
function getFeed(feed){
$.getJSON(feed, function(data) {
$.each(data.items, function(i, obj) {
$('li').append(obj.title + '=' + index + '<br>');
});
if(feedList.length > index + 1)
getFeed(feedList[++index]);
}
}
// start
getFeed(feedList[0]);
The order would be random because $.getJSON is an asynchronous request for a file. Put simply each request will take some time to complete, and your function (with the each call) will only be called for each request once each request completes respectively.
The problem arises in the fact that you cannot control which requests will return in which order. Since the all are requested at the same time.
You could get the feeds in order by waiting until each request completes before trying the next request:
var feedList [feed1,feed2,feed3,feed4,feed5];
var index = 0;
function getFeed(index) {
$.getJSON(feedList[index], function(data) {
$.each(data.items, function(i, obj) {
$('li').append(obj.title + '=' + index + '<br>');
});
if (index < feedList.length - 1)
getFeed(++index);
});
}
getFeed(0);
Try using a regular loop, combined with self executing anonymous functions. You may need to explicitly disable asynchronicity for ajax, though.
for ( var i = 0, l = feedList.length; i<l; ++i ) {
(function() {
// $.getJSON code
})();
}
Actually the inner function is probably useless. You can disable asynchronicity with
$.ajaxSetup( { "async": false } );

Categories