While loop with jQuery async AJAX calls - javascript

The thing:
I have a page, which has to display undetermined number of images, loaded through AJAX (using base64 encoding on the server-side) one by one.
var position = 'front';
while(GLOB_PROCEED_FETCH)
{
getImageRequest(position);
}
function getImageRequest(position)
{
GLOB_IMG_CURR++;
$.ajax({
url: urlAJAX + 'scan=' + position,
method: 'GET',
async: false,
success: function(data) {
if ((data.status == 'empty') || (GLOB_IMG_CURR > GLOB_IMG_MAX))
{
GLOB_PROCEED_FETCH = false;
return true;
}
else if (data.status == 'success')
{
renderImageData(data);
}
}
});
}
The problem is that images (constructed with the renderImageData() function) are appended (all together) to the certain DIV only when all images are fetched. I mean, there is no any DOM manipulation possible until the loop is over.
I need to load and display images one by one because of possible huge number of images, so I can't stack them until they all will be fetched.

Your best bet would be to restructure your code to use async ajax calls and launch the next call when the first one completes and so on. This will allow the page to redisplay between image fetches.
This will also give the browser a chance to breathe and take care of its other housekeeping and not think that maybe it's locked up or hung.
And, use async: 'false' is a bad idea. I see no reason why properly structured code couldn't use asynchronous ajax calls here and not hang the browser while you're fetching this data.
You could do it with asynchronous ajax like this:
function getAllImages(position, maxImages) {
var imgCount = 0;
function getNextImage() {
$.ajax({
url: urlAJAX + 'scan=' + position,
method: 'GET',
async: true,
success: function(data) {
if (data.status == "success" && imgCount <= maxImages) {
++imgCount;
renderImageData(data);
getNextImage();
}
}
});
}
getNextImage();
}
// no while loop is needed
// just call getAllImages() and pass it the
// position and the maxImages you want to retrieve
getAllImages('front', 20);
Also, while this may look like recursion, it isn't really recursion because of the async nature of the ajax call. getNextImage() has actually completed before the next one is called so it isn't technically recursion.

Wrong and wrong. Don't user timers, don't chain them. Look at jQuery Deferred / when, it has everything you need.
var imgara = [];
for (image in imglist) {
imgara[] = ajax call
}
$.when.apply($, imgara).done(function() {
// do something
}).fail(function() {
// do something else
});

Try using setInterval() function instead of while().
var fetch = setInterval(loadImage, 2000);
function loadImage(){
position= new position; //Change variable position here.
getImageRequest(position);
if(!GLOB_PROCEED_FETCH){
clearInterval(fetch);
}
}

Related

Wait for $.ajax result inside .each function

I have function that search for every element with a specific class:
$("#stepSurveyCtnId .questionCtnClass").each(function () {}
Inside each step, I check if a question is of type customer:
var type = $(this).children().data("question-type");
var isCustomerQuestion = false;
switch (type) {
case "Name":
case "Email":
isCustomerQuestion = true;
break;
}
If it's customer type, I get the next id of the customer's table from the database:
if(isCustomerQuestion) {
if (customerId == -1) {
$.ajax({
method: "POST",
url: urlCustomerCreate,
success: function (ajaxData) {
customerId = ajaxData.NumericValue;
}
});
}
}
The issue is that in the second iteration of the .each() function, customerId is still = -1, when it should be 1305 for example.
It seems that the execution don't stop in the $.ajax call, or the iterations are executed at the same time and the second iteration don't receive the customerId from the first iteration.
I'm still not 100% clear on sure on how everything is structured for you, but here is one way of handling asynchronicity in JavaScript (adapted from #ShubHam's answer)
function handleQuestion(questionElements, index, customerId) {
if (questionIndex >= questionElements.length) return;
var type = $(this).children().data("question-type");
var isCustomerQuestion = false;
switch (type) {
case "Name":
case "Email":
isCustomerQuestion = true;
break;
}
if(isCustomerQuestion) {
if (customerId == -1) {
$.ajax({
method: "POST",
url: urlCustomerCreate,
success: function (ajaxData) {
handleQuestion(questionElements, questionIndex + 1, ajaxData.NumericValue);
}
});
} else {
// Have ID now
handleQuestion(questionElements, questionIndex + 1, customerId);
}
}
}
// Go
handleQuestion($("#stepSurveyCtnId .questionCtnClass"), 0, -1);
This will only continue to the next iteration after the success callback has been triggered.
Put logic inside one function (say function 1) and ajax call inside other function.
Call ajax function from function 1. Inside success call function 1 with required params
Update (example added):
var x=['a','b','c']
var elm=document.getElementById('demo')
x.forEach(function(temp){
elm.innerHTML=elm.innerHTML+temp
})
<div id='demo'></div>
This can be converted to new logic as
var x=['a','b','c']
function sethtml(temp,length,maxlength){
//here ajax call can be placed
var elm=document.getElementById('demo')
elm.innerHTML=elm.innerHTML+temp
//inside success function of ajax
traverse(length+1,maxlength)
}
function traverse(length,maxlength){
if(length>=maxlength)
{
//all calls done next steps to perform
}else{
sethtml(x[length],length,maxlength)
}
}
traverse(0,x.length)
<div id='demo'></div>
Advice to be considered from Jamie-Day in comments: Check your logic for scope of improvement. Accessing db results in for each kind of scenario generally can be avoided(ideally it should be avoided for better user experience)
Change your ajax code. add "async: false" so that each code next to ajax will wait for ajax result
if(isCustomerQuestion) {
if (customerId == -1) {
$.ajax({
method: "POST",
async: false,
url: urlCustomerCreate,
success: function (ajaxData) {
customerId = ajaxData.NumericValue;
}
});
}
}
First, you need to think asynchronously.
Code that need to run after the ajax should be called from the success function. You also want to add error function to handle server errors.
Second, to improve speed and bandwidth I'd reduce number of AJAX calls to a single one, by joining all IDs together in a single AJAX request.
It require server-side changes and you did not provide the server-side, so I'll leave server side to you.
// Prepare ajax call
var customerData = [];
var customerCreateData = [];
$("#stepSurveyCtnId .questionCtnClass").each(function () {
var type = $(this).children().data("question-type");
var isCustomerQuestion = false;
switch (type) {
case "Name":
case "Email":
isCustomerQuestion = true;
break;
}
// Set customerId and customerCreateData
if(isCustomerQuestion) {
if (customerId == -1) {
customerCreateData.push(customerCreateData);
}
}
}); // end each
if (customerCreateData.length) {
$.ajax({
method: "POST",
url: urlCustomerCreate,
data: customerCreateData,
success: function (ajaxData) {
customerData = ajaxData.customerData;
doAmazingThingsWithCustomers(customerData);
},
error: function(jqXHR, textStatus, errorThrown) {
alert('Server error: ' + errorThrown);
}
});
}
The first A in AJAX stands for Asynchronous which means that the ajax calls would get executed and would not wait for the call to finish. This way we can let users interact with other elements on the page and provide a good user experience.
If we make the AJAX calls asynchronous by setting the async option to false, then the browser would wait for the ajax call to complete and users would not be able to interact with the elements until the call has completed. With the increase in number of calls, this blockage time would increase.
I would suggest you find a better alternative than this.

How to ensure that a function containing multiple $.ajax calls runs fully synchronously and also allows for browser repaints as it executes

I've been working on getting a function written to:
1) Process an input array using $.ajax calls to fill an output array (below this is inputList)
2) Below is what I have, but I'm having issues with it:
requestData(), when I call it, runs straight through to processing the outputList array without having fully populated/filled it - it puts one value into it then starts to process that, but the function still apparently runs on seperately to the subsequent processing asynchronously. I need it to be fully synchronous so that it does not return until the inputList array has been fully processed.
I'm not seeing the browser repainting the div that has its html updated on every call of the runajax() function - I'm attempting to do this with a setTimeout.
3) I've set the ajax request to be synchronous (async : false) - but this doesn't seem to help
I've tried to use jQuery's $.when to provide an ability to ensure that everything gets called in sequence - but clearly I'm not doing this correctly.
Would appreciate any help - I've asked previous related questions around this and had some useful help - but I've still not resolved this!
Thanks
//declare holding function requestData - expects a non-empty input data array named inputList
function requestData() {
//declare inner function runajax
function runajax() {
if(inputList.length > 0) {
//get first item from inputlist and shorten inputList
var data = $.trim(inputList.shift());
function getData() {
//send the data to server
return $.ajax({
url: 'sada_ajax_fetch_data.php',
cache: false,
async: false,
method: "post",
timeout: 2000,
data: {
requesttype: "getmydata",
email: encodeURIComponent(data)
}
});
}
function handleReturnedData (response) {
response = $.trim(decodeURIComponent(response));
//update the div inner html
if(response == "Failed") {
$('#fetchupdatestatus').html('There was an error retrieving the data you requested!');
} else {
$('#fetchupdatestatus').html('The item returned was '+response);
}
//add the response from ajax to the end of the outputList array
outputList.push(response);
//set up the next ajax call
var doNextBitOfWork = function () {
runajax();
};
//call setTimeout so that browser shows refreshed div html
setTimeout(doNextBitOfWork, 0);
//return
return $.when();
}
//do the next ajax request and response processing
return getData().done(handleReturnedData);
} else {
//did the last one so return
return $.when();
}
}
//kick off the ajax calls
runajax();
}
var inputList = new Array();
var outputList = new Array();
.....load +/- 100 values to be processed using ajax into array inputList
requestData();
.....process stuff in array outputList
.....etc
There was my answer with "you're doing it wrong" earlier, but then I just decided to show, how you can do it (almost) right: https://jsfiddle.net/h4ffz1by/
var request_maker = {
working: false,
queue: [],
output: [],
requestData: function(inputList) {
if (request_maker.working == true) {
return false;
}
request_maker.output = [];
request_maker.working = true;
while (inputList.length > 0) {
var data = $.trim(inputList.shift());
request_maker.queue.push(data);
}
console.log(request_maker.queue);
request_maker.doTheJob();
return true;
},
doTheJob: function() {
current_data_to_send = request_maker.queue.shift();
console.log(current_data_to_send);
if (typeof current_data_to_send != 'undefined' && request_maker.queue.length >= 0) {
$.ajax({
url: '/echo/json/',
cache: false,
method: "post",
timeout: 2000,
data: {
requesttype: "getmydata",
email: encodeURIComponent(current_data_to_send)
},
success: function(data, status, xhrobject) {
console.log(xhrobject);
request_maker.handleReturnedData(data);
},
});
} else {
request_maker.working = false;
console.log('all data has been sent');
}
},
handleReturnedData: function(response) {
console.log(response);
response = $.trim(decodeURIComponent(response));
//response= 'Failed';//uncomment to emulate this kind of response
if (response == "Failed") {
$('#fetchupdatestatus').append('There was an error retrieving the data you requested!<br/>');
} else {
$('#fetchupdatestatus').append('The item returned was ' + response + '<br/>');
request_maker.output.push(response);
}
request_maker.doTheJob();
if (request_maker.working == false) {
console.log('all requests have been completed');
console.log(request_maker.output);
}
}
}
inputList = [1, 2, 3, 4, 5, 6];
if (request_maker.requestData(inputList)) {
console.log('started working');
}
if (!request_maker.requestData(inputList)) {
console.log('work in progress, try again later');
}
Note that I've changed request path to jsfiddle's ajax simulation link and replaced html() with append() calls to print text in div. The calls are made and get handled in the same order as it is in inputList, still they don't lock user's browser. request_maker.output's elements order is also the same as in inputList.
Have in mind, that you will need to add error handling too (probably just a function that pushes 'error' string into output instead of result), otherwise any ajax error (403/404/502, etc.) will get it "stuck" in working state. Or you can use complete instead of success and check request status right there.
UPD: Answer to the question: you cannot get both. You either use callbacks and let browser repaint inbetween asynchroneous requests or you make requests synchroneous and block browser untill your code finished working.
UPD2: There actually is some information on forcing redraw, however I don't know if it will work for you: Force DOM redraw/refresh on Chrome/Mac

jquery ajax - maintain ajax call sequence

I know my questions is marked as duplicate. But the given answer is using async:false. I don't want to force synchronous requests. How do maintain async ajax call sequence ???
I don't need to replace the content. I need to append svg one after another in a sequence.
I am appending 5 svg elements in a div. All svgs are coming by ajax call. The issue is the order of those svgs. Every time they appended in different order. I want to maintain their order. Please find below my code:
FlagRow.DEFAULTS = {
flagOrder: [
Enums.flagType.INDIA,
Enums.flagType.USA,
Enums.flagType.UK,
Enums.flagType.FRANCE,
Enums.flagType.GERMANY
]
}
var container = $(document.createElement("div"));
var topic = new Array();
for (var key in this.options.flagOrder) {
topic.push(this.options.flagOrder[key]);
}
var appendFlag = function (flag) {
console.log(flag);
var svgDiv = $(document.createElement("div"));
$(svgDiv).addClass('svgDiv');
var importedSVGRootElement = document.importNode(flag.documentElement, true);
$(importedSVGRootElement).attr('viewBox', '0 0 100 125');
svgDiv.append(importedSVGRootElement)
container.append(svgDiv);
}
$.each(topic, function (i, val) {
$.when(//ajax call to get flag svg).done(function (flag ) { appendFlag(flag ); });
});
// api call to get flag svg
var deferred = $.Deferred();
$.ajax({
url: url,
type: 'get',
data: '',
dataType: 'xml',
timeout: 300000,
success: function (data) {
deferred.resolve(data);
},
error: function (e) {
console.log(':::error in flag:::', e);
},
beforeSend: function (xhr) {
xhr.setRequestHeader("Authorization", 'myapikey');
}
});
Here every time flag svg comes in different order. I want it to display it in an order of enum. And so I tried it with $.when().done(). But it's working as per my requirement.
How do I maintain order of appended svgs coming via ajax call ???
You can use async: false to mimic what you tried to do with Deferred. Since you know the order at the moment of calling your ajax requests, using placeholders as the duplicate question (for some reason they re-opened this...) suggests is your best bet.
function getAllTheFlags() {
for( var i = 0; i < 5; i++ ) {
insertPlaceHolder( i ); //inserts <div id="placeholder-i"></div> at desired location
insertFlag( i );
}
}
function insertFlag( i ) {
$.ajax( { ... } ).success( function( data ) {
var svgDiv = $(document.createElement("div"));
$(svgDiv).addClass('svgDiv');
var importedSVGRootElement = document.importNode(flag.documentElement, true);
$(importedSVGRootElement).attr('viewBox', '0 0 100 125');
svgDiv.append(importedSVGRootElement)
$( '#placeholder-' + i ).replaceWith( svgDiv );
} );
}
The function insertFlag(..) is mandatory, as you need to copy the value of i.
You can not expect async ajax call to end in order of call. But you could wrap it in a function that takes the element as parameter that you can acces in your ajax callback.
function fetchContent(element, url){
$.ajax({
url: url,
success: function(data) {
element.whatever(...);
}
});
}
In your code you then create a div or search for an existent one. And call your fetchContent by passing that element as a parameter. Even if your ajax calls don't end in the order of call the content should be added to the good element.
I think it should work.

html page doesn't have a chance to render between ajax calls

I have some 'ajax' calls (really sjax I guess you could call it) and I'm trying to make them render on the page one at a time, but they aren't. They all render at the end. How can I space them out?
function getNames() {
var names = $('#thenames').val();
alert(names);
var splitnames = names.split(',');
for(var i = 0; i < splitnames.length; i++) {
var name = splitnames[i];
$.ajax({
type: 'GET',
url: '/acert/secure/people/namesservice/getnamesajax.jsp',
data: { usernames: name},
success: function(data) { $('#results').html($('#results').html() + data);},
async: false });
}
}
}
I can't risk them coming back in the wrong order so I need them to be synchronous. I put them into a for-loop, so the for-loop should give the browser a chance to render between calls, but I can't seem to make it.
Any ideas on what I'm doing wrong?
If I add an alertbox in the success function it works, but I don't want to have to babysit the operation, I just want to monitor its progress now and again.
async: false blocks the browser. It completely locks up everything, including repaints to the DOM.
I strongly strongly recommend you don't use async: false. It is extremely bad.
You might be able to use setTimeout in-between the calls, but they don't guarantee the browser will trigger a repaint.
If you set async: true you will not have this problem, but you will likely have to change your code to properly deal with asynchronous behaviour.
async false is so bad jQuery decided to remove it from the API.
Do not use async: false.
The code below will run all ajax requests as fast as possible, then append the content to #results in the correct order. DO NOT include async: false if you are using the code below.
var defArr = [];
for(var i = 0; i < splitnames.length; i++) {
defArr.push( $.ajax({...}) );
}
$.when.apply($,defArr).done(function(){
var $results = $("#results");
$results.empty();
for (var i = 0; i < arguments.length; i++) {
$results.append(arguments[i][0]);
}
});
assuming you know how many calls you make (or can include that as a parameter to the return result) you can simply fire the calls asynchronously, and make them elements in an array on your success callback. When the array gets to the expected size, just render them in sequence.
For one, you seem to have an extra curly brace there.
But more to the issue at hand, if you just want to monitor the progress would it work for you to use setTimeout?
-- update --
I think I get what you're trying to do. And if I'm not mistaken, you could refactor a little, and then use a closure and an object with the names as the keys. Something like this:
function getNames()
{
var names = $('#thenames').val();
var splitnames = names.split(',');
var myData = {};
for(var i = 0; i < splitnames.length; i++)
{
(function(name)
{ return function(){
$.ajax({
type: 'GET',
url: '/acert/secure/people/namesservice/getnamesajax.jsp',
data: { usernames: name},
success: function(data) { myData[name] = data; updateNames(); }
});
})( splitnames[i] )
}
}
What this basically does is that it sets up a bunch of ajax calls right away, that weird bit in the middle with the (function(){})() makes sure you don't end up fetching the last value that name gets set to when the loop finishes. Everything gets saved to myData, I figured once everyone is loaded you could check to see if all the names you have in splitnames are in myData with the updateNames function. Something like
var count = 0;
for ( var i = 0; i < splitnames.length; i++ )
{
count += myData[splitnames[i]] != null ? 1 : 0;
}
if (count == splitnames.length)
{
// write the names to the screen
}
Does that make sense?
But, to be honest, the best approach would probably be to change the getnamesajax.jsp so that it accepts all the names, and then gives you back the info you need in the order you need. If that was an option, that would be best since you would only need to make one ajax call.

jQuery ajax function return

I have this function that embeds flash :
function embedswfile(target, swf, base, width, height) {//dosomething}
And I want to call the function like this
embedSwf("flashgame",decode("<?=base64_encode($path['location'])?>"),decode("<?=base64_encode($path['base_directory'])?>"),"800","600" )
The idea is that whenever someone looks for any swf inside my website,he wont find anything clean.I will change the encoding algorithm,but this is just temporary. In order for that function to work,whenever I call the function 'decode' it must return a single value. PHP contains
<?php
echo base64_decode($_POST['s']);
?>
I tried this but it still wont work
var globvar;
function processdata(newmsg) {
globvar = newmsg;
}
function decode(s){
$.ajax({type: "POST",
url: "includes/decode.inc.php",
data: "s=" + s,
success:function(newmsg){
processdata(newmsg);
}
});
return globvar;
}
Important:
Forget about using Ajax and encoding, decoding the path. What do you think you gain from it? Security? No. One can figure out that this is bas64 encoded or he just monitors the network traffic and reads the response from the Ajax call.
Just do
embedSwf("flashgame","<? =$path['location']?>"),"<?=$path['base_directory']?>","800","600" )
Really, you cannot prevent someone else seeing the data and are just making things more complicated for you.
(Or you have to decrypt the data with JavaScript.)
(original answer is still correct nevertheless)
Ajax is asynchronous so something like var test = decode(s); will never work. The decode function will return before the Ajax call finishes.
Instead, put your logic into the callback handler. For example, if your code was this before:
var retdata = decode('s');
// here comes code that handles retdata
put the code into a function and call it from the success handler:
function process(retdata) {
// here comes code that handles retdata
}
function decode(s){
$.ajax({type: "POST",
url: "includes/decode.inc.php",
data: "s=" + s,
success:function(newmsg){
process(newmsg);
}
});
}
This seems to be a very common problem to all beginners. You will find a lot of questions here that deal with the same problem.
Update:
It is not nice, but you could change the function to
function decode(s, cb){
$.ajax({type: "POST",
url: "includes/decode.inc.php",
data: "s=" + s,
success:function(data){
cb(data);
}
});
}
and do
decode("<?=base64_encode($path['location'])?>", function(location) {
decode("<?=base64_encode($path['base_directory'])?>", function(dir) {
embedSwf("flashgame",location,dir,"800","600" );
});
});
Update 2:
For completeness, you can make the Ajax call synchronous, by using async: false. Then this will work:
function decode(s){
var ret;
$.ajax({type: "POST",
url: "includes/decode.inc.php",
data: "s=" + s,
async: false,
success:function(newmsg){
ret = newmsg;
}
});
return sync;
}
var val = decode(s);
However, this will block the browser until the Ajax call finished. You have to test whether this matters in your case or not.
Update 3:
You could also change your PHP script to not only accept one parameter but several and process both strings in one go.

Categories