JQuery/Ajax and when/done/promise confusion - javascript

I am trying to accomplish the following:
1) Get data some source and "do something with it".
2) Get data some other source and "do something with it".
3) The datafetching should preferrably run asynchronous (at the same time ie. the second one should not wait for the first one to complete).
4) When both are completed, some business logic runs - but ONLY when they are completed.
I have created a small JSFiddle to show how I thought this could work - but unfortunately it does not:
a) The datafetching calls are executed sequentially.
b) The business logic from step 4 above executes before the datafetching has even begun...
Fiddle here: https://jsfiddle.net/LeifFrederiksen/emttmhm7/
$.when(
getOneThing(),
getAnotherThing()
).done(
function() {
console.log("Got it all");
$("#Output").append("<BR>Got it all");
}
);
function getOneThing() {
commonFunctionToGetStuff("oneKindOfThings",renderOneKindOfThings);
}
function getAnotherThing() {
commonFunctionToGetStuff("anotherKindOfThings",renderAnotherKindOfThings);
}
function commonFunctionToGetStuff (listTitle,successFunction) {
var url = "https://httpbin.org/get";
$.ajax({
url: url,
type: "GET",
headers: { "accept": "application/json;odata=verbose" }
}).success(function (data) {
console.log("Calling renderfunction for " + listTitle);
$("#Output").append("<BR>Calling renderfunction for " + listTitle);
successFunction(data);
console.log("Back from renderfunction for " + listTitle);
$("#Output").append("<BR>Back from renderfunction for " + listTitle);
});
}
function renderOneKindOfThings(data) {
// Do something with the data...
console.log("Doing oneKindOfThings.");
$("#Output").append("<BR>Doing oneKindOfThings.");
}
function renderAnotherKindOfThings(data) {
// Do something with the data...
console.log("Doing anotherKindOfThings.");
$("#Output").append("<BR>Doing anotherKindOfThings.");
}
Any help clearing up how the structure should be is highly appreciated.
I need to maintain the structure where the function that performs the actual Ajax call is kind of generic, and can be called by simple wrapper functions with parameters controlling what datasource to use - like it is in the example :-)
Regards
Leif

You need to return the promise from your commonFunctionToGetStuff-method and the methods that call it. Otherwise, you're passing in undefined into your when-function which will immediately execute the done-callback. Also you have a few erroneous callback names (it's done or then, not success).
function getOneThing() {
return commonFunctionToGetStuff("oneKindOfThings",renderOneKindOfThings);
}
function getAnotherThing() {
return commonFunctionToGetStuff("anotherKindOfThings",renderAnotherKindOfThings);
}
function commonFunctionToGetStuff (listTitle,successFunction) {
var url = "https://httpbin.org/get";
return $.ajax({...})
.then(function (data) { ...});
}

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.

Waiting for ajax response same function

I know that similar questions have been posted many times, however I've read many of them and can't find an answer to my problem.
I have a function that waits for an ajax request response. Many of you will ask why? Well, I'm using a Wizard Jquery Plugin which executes a function onLeaveAStepFunction when a step is left, then the wizard goes to the selected step if the return value from onLeaveAStepFunction is true; else it remains in the same step.
I'm doing this async: false for waiting and it works, but this is a bad design. Also, I can't use a blockUI plugin.
How can I do this?
Some code:
Initializing the wizard:
$("#wizard").smartWizard({
onLeaveStep : onLeaveStepFunction,
});
Calling the ajax request:
function onLeaveStepCallback(obj, context) {
nextStep = sendForm();
}
The ajax request:
var nextStep = false;
$.ajax({
url : path,
type : "POST",
async : false,
data : $("#" + idForm).serialize(),
success : function(data) {
$("#" + idDiv).html(data);
nextStep = !$("#" + idHiddenErrores).val())
}
});
Omitting the attributes. Please help me.
You could use the jQuery wait method. I took an example from docs page to highlight how you'd do it:
$.when( $.ajax( "/request.php" ) ).done(function( response ) {
// response argument resolved from ajax requests
// process any work after ajax call finishes
}
A link to docs page:
http://api.jquery.com/jquery.when/
I'm doing this async: false for waiting and it works, but this is a bad design also I can't use a blockUI plugin.
Unless your wizard is better designed and supports async callbacks (e.g., promise-returning ones), async:false is your only choice.
Consider switching to a different wizard, and don't forget to file a bug for the plugin that you're currently using.
One hackish work-around is to do it before leaveStep. Perhaps on showStep:
var wizard_next_step;
$("#wizard").smartWizard({
onShowStep : function (obj, context) {
onLeaveStepFunction(obj, context, function(nextStep){
wizard_next_step = nextStep;
});
},
onLeaveStep : function () {
return wizard_next_step;
}
});
You'd also need to modify your onLeaveStepFunction to accept a callback:
function onLeaveStepCallback(obj, context, callback) {
nextStep = sendForm(callback);
}
And your ajax function should then be:
$.ajax({
url : path,
type : "POST",
async : false,
data : $("#" + idForm).serialize(),
success : function(data) {
$("#" + idDiv).html(data);
callback( !$("#" + idHiddenErrores).val()) );
}
});
Now, it looks like you're drawing into the wizard window with this:
$("#" + idDiv).html(data);
I'm entirely sure if this is the case. But if it is then you cannot do this here (obviously because it's onShowStep which would overwrite current content). If this is so you should pass the data in the callback:
success : function(data) {
callback( data , !$("#" + idHiddenErrores).val()) );
}
Write the wizard like this:
var wizard_next_step;
var wizard_data;
$("#wizard").smartWizard({
onShowStep : function (obj, context) {
onLeaveStepFunction(obj, context, function(data, nextStep){
wizard_data = data;
wizard_next_step = nextStep;
});
},
onLeaveStep : function (obj, context) {
$("#" + idDiv).html(wizard_data);
return wizard_next_step;
}
});
The key is to call all the asynchronous functions and get the data long before you call all your synchronous functions.
Note: I don't know smart-wizard at all and not a serious jQuery user. The answer above is based on my 2 minutes reading smart-wizard documentation on github and my understanding of javascript. You will definitely need to modify my examples to make it work.

While loop with jQuery async AJAX calls

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

my function can't pass it's parameter value javascript

Ive been struggling to pass my parameters from a functions but I just really can't figure out where did I go wrong. I have a function that have a parameters that I want to pass to my postData to display datas in my jQgrid. Here's my function code with parameters:
function getTID(hdrID){
var selected = $('#editTallyHdr').val();
var hdrID = '';
var hdrNo = '';
var nameFlag=0;
var par_ams = {
"SessionID": $.cookie("SessionID"),
"dataType": "data"
};
$.ajax({
type: 'GET',
url: 'processjson.php?' + $.param({path:'getData/tallyHdr',json:JSON.stringify(par_ams)}),
dataType: primeSettings.ajaxDataType,
success: function(data) {
if ('error' in data)
{
showMessage('ERROR: ' + data["error"]["msg"]);
}
else{
$.each(data['result']['main']['rowdata'], function(rowIndex, rowDataValue) {
$.each(rowDataValue, function(columnIndex, rowArrayValue) {
var fldName = data['result']['main']['metadata']['fields'][columnIndex].name;
if (fldName == 'transaction_id'){
hdrID = rowArrayValue;
}
if (fldName == 'transaction_num'){
hdrNo = rowArrayValue;
if(selected == hdrNo){
nameFlag =1;
};
}
});
});
}
}
});
return (hdrID);
}
and here is my jQgrid code where I call that function to get it's parameter:
$("#tblPlank").jqGrid({
url: '',
datatype: 'local',
jsonReader : {
.
.
.
serializeGridData: function(postData) {
var ctr =0;
var filt=[];
var c=[];
var jsonParams = {
'SessionID': $.cookie("SessionID"),
'dataType': 'data',
'transaction_id':getTID(hdrID),
'filters': c,
'lines':plank_data,
'recordLimit': postData.rows,
'recordOffset': postData.rows * (postData.page - 1),
'rowDataAsObjects': false,
'queryRowCount': true,
'sort_fields': postData.sidx
};
.
.// some code here
.
.
return 'json=' + JSON.stringify(jsonParams);
},
loadError: function(xhr, msg, e) {
showMessage('HTTP error: ' + JSON.stringify(msg) + '.');
},
colNames:[...],
colModel:[
........................
],
.
.
.
caption: "Tally Transaction Details/Lines"
I also have another code where I want to get that parameter. Here's the last code:
var par_ams = {
"SessionID": $.cookie("SessionID"),
"dataType": "data",
"transaction_id": getTID(hdrTID)
}
$('#tblPlank').setGridParam({
url:'processjson.php?path=' + encodeURI('getData/tallyLnDtl') + '&json=' + encodeURI(JSON.stringify(par_ams)),
datatype: primeSettings.ajaxDataType,
});
$('#tblPlank').trigger('reloadGrid');
Those codes below that function getTID(hdrID) cant retrieve the parameter, it shows empty. This maybe simple to anyone, but I really need help on this.. been working with this for quite long hours.
This is a very common misunderstanding. I've probably answered 15 of these questions in the last two weeks alone. An ajax call is an asynchronous call. That means that when you make the ajax call, it just STARTs the request. Then, while that request goes in the background, your code immediately keeps executing. That means that your function getTID() returns before the ajax call has even completed and it's value is not yet known. Thus, there is no way to return the response value from the ajax function when you return from getTID() as it is simply not known yet.
To work with asynchronous function calls (like ajax calls), you have to change your programming style to something that works asynchronously. In this case, the response to your ajax call is ONLY known in the success handler for the ajax all. So, you have to restructure your code to continue on with the execution of your processing and the handling of the ajax response from the success handler. If you have only a little bit of work to do, then you can put it all in the success handler. If you have a lot of work to do, then you can put all the rest of that work in a function call and call it from the success handler.
The problem is that you're doing an ajax-request (asynchronous request). Then the function does not wait for an answer to arrive, but just continues and returns hdrID (which isn't set at the time). After that a response comes in, and the success-method is called, which sets hdrID to the appropiate value.
The common way to solve this is to execute a specific function with the desired values when the success-method is executed. It's too much code to look into, but it could go something like this:
function fetchContent(continueFunction) {
$.ajax(params).success(function(reply) {
// retrieve desired params from reply
continueFunction(retrievedParameters);
}
}
What you could do is define getTID to take in a callback to execute once it has the id, for instance
function getTID(hdrID, callback) {
//ajax stuff....
success: function (data) {
// Error checks, etc
hdrID = //something dependent on data
callback(hdrID); // THIS IS THE IMPORTANT PART
}
the callback will execute after the request has returned, when it is safe to use the data returned from the ajax request that will be needed in the callback. You could wrap all of the code that needs the return value of the request in the callback, for example
getTID(hdrID, function (ID) {
var params = {
"SessionID": $.cookie("SessionID"),
"dataType": "data",
"transaction_id": ID //USE ID
}
$('#tblPlank').setGridParam({
url:'processjson.php?path=' + encodeURI('getData/tallyLnDtl') + '&json=' + encodeURI(JSON.stringify(par_ams)),
datatype: primeSettings.ajaxDataType,
});
$('#tblPlank').trigger('reloadGrid');
};
});

jQuery $.post() in recursive way (loop)

I learn jQuery and don't understand this situation:
When running this code in debug mode all work well. But when running this code normal, calback function don't starts. Why?
In non debug mode I have -> "Start" -> "End 10"
Browser: Google Chrome.
var nrPost = 10;
$("#buttnX").click(function() {
alert("Start");
GoPosts();
End();
});
function End() {
alert('End ' + nrPost);
};
function GoPosts() {
$.ajaxSetup({async:false});
var url = "http://......";
var data = { ... };
$.post(url, data, Callback, 'json');
};
function Callback(response) {
if (response.error) {
return;
}
nrPost--;
if (nrPost > 0) [
GoPosts();
} else {
return;
}
};
You had an extra }; in your code. I changed it around a bit to use jQuery and put it up on jsfiddle.
http://jsfiddle.net/rH8RV/19/
It should alert: "Start" and then "End 10", that's correct based on how you wrote your code. Were you expecting anything else?
I don't know what you're planning to do with your recursive implementation, but if that's all, you could actually do this:
function startLoop(nrPost) {
// No need to put this in a loop
$.ajaxSetup({ async: false });
for (var i = 0; i < nrPost; i++) {
alert('Start ' + i);
var url = 'http://......';
var data = {};
$.post(url, data, function (response) {
if (response.error)
return;
alert('End ' + i);
}, 'json');
}
}
$('#buttnX').click(function () { startLoop(10) });
Hope that helps!
I imagine you are expecting the display to be:
"Start"
"End 0"
This is unlikely to work with your solution.
Your Ajax call $.post(url, data, Callback, 'json'); is asynchronous. This means that once the $.post method returns, the request is sent to the URL you have provided. However, Callback is not called until JQuery receives the answers. What happens immediately is that GoPosts terminates and the program continues. It comes back to line 5 of your code, inside the anonymous function in your click handler. At that point, End() is called and alerts "End 10".
You probably want to put your call to End in Callback instead:
function Callback(response)
{
if (response.error)
{
return;
}
nrPost--;
if(nrPost>0)
GoPosts();
else
{
End(); // You only want to end once you have made you nrPost calls to GoPost
return;
}
};

Categories