I'm trying to code some JavaScript with jQuery that executes some asynchronous events sequentially (in a loop); only iterating afte the previous async event has completed.
The basic idea is:
for (var i = 0; i < someLength; i++) {
// 1. dynamically add a <form> and <input>s to the DOM
// 2. submit the form to a target <iframe>
// 3. wait for an async callback indicating the form has been submitted
}
I have working code for #1 and #2, and I can attach a load event listener to the <iframe> to know when the form submit has completed:
$('.iframe').on('load', function() { ...
});
for (var i = 0; i < someLength; i++) {
// 1. dynamically add a <form> and <input>s in the DOM
// 2. submit the form to a target <iframe>
// 3. wait for an async callback indicating the form has been submitted
}
The problem is I don't know how to tie the ready callback to the loop logic. I tried to look into deferreds/promises, but didn't really see how to correctly apply those concepts here.
If the answer is to use promises, then I would love to also see how to incorporate some code to show a "loading" spinner while the loop is going, and then remove the spinner once we're out of the loop.
Thanks!
For-loop dislike asynchronous operations especially if you want to chain them. A solution may look like this.
$('.iframe').on('load', function() { ...
// increment i
//if(i >= someLength) exit
// 1. dynamically add a <form> and <input>s in the DOM
// 2. submit the form to a target <iframe>
});
//for (var i = 0; i < someLength; i++) {//no loop
//}
// 1. dynamically add a <form> and <input>s in the DOM
// 2. submit the form to a target <iframe>
// 3. nothing to wait
I think you can do it in recursive way instead of a for loop. something like this-
$("#imgSpinner").show();
someFunction(0,someLength);
function someFunction(currentSeq,totalIterations)
{
$.ajax({
url: URL,
type: 'POST',
contentType: 'application/json; charset=utf-8',
success: function (result) {
if (currentSeq<totalIterations)
someFunction(currentSeq++,totalIterations);
else
$("#imgSpinner").hide();
},
error: function (error) {
//handle error
}
});
}
I'm trying to understand how you can have a for-loop creating forms and fields... are all your forms containing the same fields? Ie multiple email addresses or something?
Without understanding the design, I'll give you this as an example of submitting some data then when that finishes submitting the next data.
$.when(AjaxSubmitFirstData.call()).done(function (data) {
$.when(AjaxSubmitSecondData.call()).done(function (data) {
// finished
});
});
var AjaxSubmitFirstData = {
call: function () {
return $.ajax({
url: url,
type: 'POST',
data: "{}",
contentType: 'application/json; charset=utf-8',
}).done(function (data) {
}).fail(function (jq, textStatus) {
});
}
};
var AjaxSubmitSecondData = {
call: function () {
return $.ajax({
url: url,
type: 'POST',
data: "{}",
contentType: 'application/json; charset=utf-8',
}).done(function (data) {
}).fail(function (jq, textStatus) {
});
}
};
Related
In the below code I am making an API call to my backend node.js app using setTimeout() which calls my AJAX at every 5 seconds. Inside my AJAX success I am displaying divContent1 & divContent2 based on certain condition which should execute at least once. After that only divContent2 should be visible at each setTimeout() calls.
index.html
<script type="text/javascript">
$(document).ready(function(){
$.ajax({
url: "http://localhost:8070/api/route1",
type: 'POST',
dataType:'json',
success: function(res) {
//Some Task
}
});
$("#myButton").click(function(){
const route2 = function() {
$.ajax({
url: "http://localhost:8070/api/route2",
type: "POST",
dataType: "json",
data: { var1: val1 },
success: function (res) {
// Various tasks
if(res.flag){
$("#divContent1").hide();
$("#divContent2").show();
}
else{
$("#divContent1").show();
}
//Functions that handle div content data
},
beforeSend: function() {
$("#divContent1").hide();
$("#divContent2").hide();
},
complete: function() {
setTimeout(route2,5000);
},
});
};
$(function(){
route2();
})
});
});
</script>
The setTimeout() calls the entire route2 function which handles all the display and insertion of div content. However, the ask is to only display divContent2 from the second call.
Looking for a solution for this
The setTimeout() calls the entire route2 function which handles all
the display and insertion of div content. However, the ask is to only
display divContent2 from the second call.
You're calling route2 recursively with setTimeout(route2,5000); under complete. So this will run infinitely as complete occur each time an ajax call is completed (wether success or error). So what you can do is to create a timer and clear it after the second execution, something like this:
var ctr = 0, timer =0;
const route2 = function() {
$.ajax({
...
success: function (res) {
//Write you logic based on ctr
}
complete: function() {
if(ctr>0){
clearTimeout(timer)
}else{
timer = setTimeout(route2,5000);
ctr = ctr+ 1;
}
},
});
};
Will an external variable be enough? Just define it in the outer context and set/check it to choose the behavior:
// before declaring button click handler
var requestDoneAtLeastOnce = false;
// ...
// somewhere in success handler
success: function (res) {
if (!requestDoneAtLeastOnce) {
requestDoneAtLeastOnce = true;
// do something that belongs only to handling the first response
}
else {
// this is at least the second request, the other set of commands belongs here
}
}
I have the following JavaScript code:
Interface.init = function()
{
$.ajax({
type: "POST",
url: "/Validate",
async: false,
success: function (data) {
if (data.Valid) {
// All good, continue executing JS code
}
else {
// Display error messsage, attempt to stop executing JS code...
return false;
}
},
error: function () {
// Display error message, attempt to stop executing JS code...
return false;
}
});
// More JavaScript functions used to load content, etc...
}
The index page calls Interface.init() on load:
<html>
<script type="text/javascript">
$(document).ready(function () {
Interface.init();
});
</script>
</html>
The ajax function is used to check if the device loading the page is valid. It is run synchronously so the page waits for the validation to complete before continuing. If the validation is successful, the ajax function is exited and the rest of the JavaScript code continues to execute. If the validation fails (or there is an error during the validation), I don't want any of the remaining JavaScript code to be executed.
I'm currently using return false, but I've also tried just return and throwing an error such as throw new Error("Validation failed") (as suggested by numerous other questions).
All these seem to do is exit the ajax function and all remaining JavaScript on the page continues to execute. Outside of the ajax function, these methods work as expected to stop the remaining code from executing, but I was hoping for this to be done from within the ajax function. Is this at all possible?
You can create an outside variable before the function and use it after it, e.g:
Interface.init = function()
{
var error = false;
$.ajax({
type: "POST",
url: "/Validate",
async: false,
success: function (data) {
if (data.Valid) {
// All good, continue executing JS code
}
else {
error = true;
}
},
error: function () {
error = true;
}
});
if (error) return;
// More JavaScript functions used to load content, etc...
}
But in fact, I recommend to not use the async=false, and instead of that, you could wrap the rest of your code in a function, and call it inside the callback, e.g:
Interface.init = function()
{
$.ajax({
type: "POST",
url: "/Validate",
success: function (data) {
if (data.Valid) {
// All good, continue executing JS code
loadAll();
}
},
error: function () {
}
});
function loadAll() {
// More JavaScript functions used to load content, etc...
}
}
In order to prevent getting an error twice I use beforeSend.
hasSent = false
function submit() {
if (!hasSent)
$.ajax({
url: "${createLink(controller:'userInvitation', action:'ajaxUpdate')}",
type: "POST",
data: $("#invitationForm").serialize(),
success: function(data, textStatus, jqXHR) {
$('#invitationForm')[0].reset();
$('.thank-you-modal').modal('show');
hasSent = true;
console.log(hasSent)
},
complete: function() {
hasSent = false;
console.log(hasSent)
}
});
}
As you can see the ajax should happen only if hasSent=false.
For some reason the ajax happens also if the user clicks multiple time (very quick) on the submit button
To prevent this kind of issue disable the button before sending the ajax and then anable inside the success function
$(mybutton).prop("disabled",true);
// ajax call here
then
success: function(data, textStatus, jqXHR) {
$(mybutton).prop("disabled",false);
// code here
}
You can create another flag such as isSending
function submit() {
if(isSending)
return;
isSending = true
$.ajax({
// ...
complete: function() {
isSending = false;
}
});
}
there are two ways you can do this.
1) create a flag and check if the button is pressed. If pressed then do not execute the ajax code
change the flag back once the request is successful, like this
success:function(...)
{
flag=false;
}
Or you can disable the button at the button click so the request will be carried out and double click situation won't arise. Enable the button on complete like this
complete:function(..){ $("yourbutton").attr("disabled",false)}
Note: simplified example..
I've got a page with 1000 table rows. For each row, i need to "do some work" on the server via an AJAX call, then in the callback, update that table row saying done.
Initially i tried just firing off the 1000 ajax requests inside the .each selector, but the browser was locking up.
So i changed it to try and use an internal ajax counter, so only ever fire off 50 at a time.
Here's the code:
$('#do').click(function () {
var maxAjaxRequests = 50;
var ajaxRequests = 0;
var doneCounter = 0;
var toDo = $('#mytable tr').length;
$.each($('#mytable > tr'), function (i, v) {
while (doneCounter < toDo) {
if (ajaxRequests <= maxAjaxRequests) {
ajaxRequests++;
doAsyncStuff($(this), function () {
ajaxRequests--;
doneCounter++;
});
} else {
setTimeout(function() {
}, 1000);
}
}
});
});
function doAsyncStuff(tr, completeCallback) {
$.ajax({
url: '/somewhere',
type: 'POST',
dataType: 'json',
data: null,
contentType: 'application/json; charset=utf-8',
complete: function () {
completeCallback();
},
success: function (json) {
// update ui.
},
error: function (xmlHttpRequest, textStatus, errorThrown) {
// update ui.
}
});
}
But the browser is still being locked up. It never goes into the $.ajax complete callback, even though i can see the request coming back successfully (via Fiddler). Therefore its just sleeping, looping, sleeping, etc because the callback is never returned.
I've got a feeling that the entire doAsyncStuff function needs to be asynchronous?
Any ideas on what i am doing wrong (or how i can do this better)?
You are doing a while loop inside the .each callback function, so there is much more ajax request than 1000, the worst is 1000*1000.
You could delay each ajax request with different time.
$('#do').click(function () {
$('#mytable > tr').each(function (i, v) {
var $this = $(this);
setTimeout(function () {
doAsyncStuff($this, function () {
console.log('complete!');
});
}, i * 10);
});
});
The browser gets locked because of the WHILE... You are creating an endless loop.
The while loops runs over and over waiting for the doneCounter to be increased, but the javascript engine cannot execute the success call of the ajax since it is stuck in the while...
var callQueue = new Array();
$('#mytable > tr').each(function(key,elem){callQueue.push($(this));});
var asyncPageLoad = function(){
var tr = callQueue.splice(0,1);
$.ajax({
url: '/somewhere',
type: 'POST',
dataType: 'json',
data: null,
contentType: 'application/json; charset=utf-8',
complete: function () {
completeCallback();
asyncPageLoad();
},
success: function (json) {
// update ui.
},
error: function (xmlHttpRequest, textStatus, errorThrown) {
// update ui.
}
}
};
asyncPageLoad();
This will call the requests one by one. If you want, simply do a for() loop inside to make maybe 5 calls? And increase the amount if the browser is fine.
Actually, I prefer to send new request when current request is done. I used this method to dump db tables (in this work). Maybe it gives an idea.
See this link, check all check boxes and click Dump! button. And you can find the source codes here (see dumpAll function).
I have some image buttons that have jQuery event handlers attached to them.
I want to make an AJAX call in the click() event to determine if the action should actually be allowed. Because I am doing an async ajax call I have to return 'false' to immediately cancel the event and wait for the response.
$('[id^=btnShoppingCartStep_]').click(function() {
var stepName = this.id.substring("btnShoppingCartStep_".length);
$.ajax({
type: "POST",
url: $.url("isActionAllowed"),
data: "requestedStep=" + stepName,
dataType: "json",
success: function(data) {
if (data.allowed) {
// need to resume the action here
}
else {
AlertDialog(data.message, "Not allowed!");
}
}
});
return false;
});
I need to find the best way to resume the click event if the AJAX call determines that it should be allowed.
Any way of doing this that I can come up with seems clumsy, such as :
1) remove event handler from the buttons
2) simulate a click on the button that was clicked
Is there any nice way of doing this that I'm missing?
How long does it take to confirm the validity of the request? What else would be the user be doing during that time?
Why not have the server do the validation? Is that too risky? You could simply pass the data along to the server, have it determine if it's valid. If it's not, it tells Ajax "not allowed", the function alerts the user. If it liked the data, there is no need to resume the action as it's already done.
if you need submit form:
$('#form_id').submit();
if you need continue this function:
var allowed_actions = {};
$('[id^=btnShoppingCartStep_]').click(function() {
var stepName = this.id.substring("btnShoppingCartStep_".length);
var $click_me_again = $(this);
if (!allowed_actions[stepName]) {
$.ajax({
type: "POST",
url: $.url("isActionAllowed"),
data: "requestedStep=" + stepName,
dataType: "json",
success: function(data) {
if (data.allowed) {
// need to resume the action here
allowed_actions[stepName] = true;
$click_me_again.click();
}
else {
AlertDialog(data.message, "Not allowed!");
}
}
});
return false;
} // end if
// continue function (check passed)
});
Assign an ID to your form. Let's say you name it myForm. Where your // need to resume the action here is, use this:
$("#myForm").trigger("submit");
1) Simply call the form's submit() method.
$("#FormID").submit();
2) Also, if what you want is to intercept form submissions for that for, you may want to do this slightly differently (e.g. if you can submit the form via >1 button):
$("form").submit(function() {
...