I've got a tool in place which is splitting a large query into manageable chunks, then using a simple AJAX method to spit this out. The destination for the AJAX form is just a script which delegates some form data to a function, including which 'chunk' to process.
<script>
var passes = Math.ceil($max / $offset);
for (i = 0; i < passes; i++)
{
$.ajax({
type: 'POST', url: 'do.php?p=' + i, data: $('#form" . $i . "').serialize(),
success: function(data){
$('#update" . $i . "').append(data);
}
});
}
</script>
As this can iterate a few times, I was looking to execute a script for when the looping (i.e. the function itself) has finished.
As this isn't anything too snazzy, I thought it would be a simple case of adding if(i == passes -1) { alert('test');}if(i == passes -1) { alert('test');} to the end of the loop, like this:
for (i = 0; i < passes; i++) {
$.ajax({
type: 'POST', url: 'do.php?p=' + i, data: $('#form" . $i . "').serialize(),
success: function(data){
$('#update" . $i . "').append(data);
}
});
if(i == passes -1) { alert('test');}
}
....but it loads this as soon as the page loads, before the loop.
Likewise, adding a simple function after the loop acheives the same result, too.
I would have thought (but I'm quite fresh at JS) that it would complete a loop before attempting to execute the second instance of 'i', but it doesn't seem to do so - the page acts like all of the requests are sent instantly, completing the loop, executing the code, then allowing the functions within 'success' to echo back in their own time. This seems even more evident in that sometimes it will append the results for the second iteration of i before the first.
Questions...
1) Have I made an error in how I've constructed the loop, considering the purpose?
2) Why does the loop seem to execute code after the loop when it seems like it is still processing the loop itself?
What I'm trying to achieve
Each loop should perform a MySQL query, return the function's HTML output, then print it before moving on to the next. It does do this 99% correct, just with the occasional problem of it not always appending in order.
After all loops have completed and appended to the container, I would like to run some code to confirm that the operation is complete.
Many Thanks in advance, hope this is clear enough
This is a "Promise" based solution to your problem.
First, decompose each pass into a function that does one unit of work:
function makePass(i) {
return $.ajax({
type: 'POST', url: 'do.php?p=' + i, data: $('#form' + i).serialize()
}).then(function(data) {
$('#update' + i).append(data);
});
}
Then you can make a general purpose function that pseudo-recursively makes the desired number of calls to the supplied function, calling it each time with the current pass number, and finally returning a new "resolved" promise once every pass has been completed:
function makeNPasses(f, n) {
var i = 0;
return (function loop() {
if (i < n) {
return f(i++).then(loop);
} else {
return Promise.resolve();
}
})();
}
You can then register a handler to be invoked once everything is done:
var passes = Math.ceil($max / $offset);
makeNPasses(makePass, passes).then(function() {
console.log("All done!");
});
Javascript has an async flow.. It doesn't wait for the above request to get the data from somewhere... Instead it just keeps firing statements in a row.
To escape this, there are 3 options.
The ideal approach is to make 1 single http request to server and get all data in form of json array. This will be more efficient, easy and time-saving and also follows the best practices.
Make an async call of ajax. You will get the good information about it in this answer jQuery ajax success anonymous function scope. But again.. Callbacks are recommended and not doing async false. Instead .when() or .then() is easier.. because ultimately they too are callbacks.
Recursive Function can help you through such kind of tasks. It is a dirty approach because ES5 Doesn't let you iterate much deeper using recursive functions. But ES6 does allow it to be 10000 iterations. But its not a good approach. It increases overhead and bottleneck your page load.
AJAX calls are asynchronous and therefore they cannot succeed before the loop ends (as JavaScript is non blocking). In fact what you are wanting to do is to perform action after AJAX calls, not just after the loop (which is synchronous) so I would suggest either using Promise or chaining or aggregating success events like below:
var passes = Math.ceil($max / $offset);
for (i = 0; i < passes; i++) {
$.ajax({
type: 'POST',
url: 'do.php?p=' + i,
data: $('#form' + i).serialize(),
success: function(data){
$('#update' + i).append(data);
resolve();
},
error: reject
});
}
var resolved = 0;
function resolve() {
if(++resolved >= passes) {
alert('test');
}
}
function reject() {
alert('One of AJAX requests failed');
}
Related
I have a page with many html forms and I need to go through all of them, submit them and wait for the response, then grab the data.
The natural thing to do is to write a loop like this:
for (i = 0; i < win.document.forms.length; i++)
{
// Submit form i and wait for the response
}
But then the problem dawns on you: How do you wait for the response inside the loop? You can check whether the data is available, and if not? How do you kill time inside the loop? There is no sleep function in js, right?
To run in a loop and check for the data all the time would suck up all the system resources. You can't do that either. My current wisdom is that you need to exit the loop and terminate, but schedule the containing function for execution later on. When the function runs at some later time you must reenter the loop at the point where you left off and once again check for data available.
It's awkward to say the least. Am I missing something? Is there a better solution?
You can use just ajax asynchronous calls with callbacks so you are not blocking thread.
$("form").each(function (index, element) {
var form = $(element);
$.ajax({
url: form.attr('action'),
type: 'POST',
data: form.serialize(),
success: function (result) {
// ... Process the result ...
}
});
});
If you want to use for loop then you will need to also create outer function because variable defined in that loop will not be lexically scoped, so you can end up with submiting same form multiple times.
I have this inside an ajax function
$.each(data['Result'][0], function(Key, Value) {
InputGen(Value['Type'], Value['Name'], Value['Other'], Value['Value'], function(Html){
Table = Table + "<tr><td>"+Key+"</td><td>" + Html + "</td></tr>";
});
});
and InputGen has a callback from another ajax function but when i run this the loop does not seem to be waiting for the ajax to finish. how would i achieve this?
...the loop does not seem to be waiting for the ajax to finish.
No, because the "a" in "ajax" stands for asynchronous; it doesn't happen when you make the call, it happens later. But there's no reason the $.each loop would know to sit around and wait for it to finish.
If you don't need it to (e.g., it's okay for the ajax calls to overlap, which normally it should be unless they rely on each other), look at Rocket Hazmat's approach.
If you need each ajax call to wait for the previous one to finish, you can't use $.each (or at least, not the way you were); instead, use an index and respond to the callback by triggering the next request:
// Start with first entry
var index = 0;
var array = data['Result'][0];
// Do the first request
doRequest();
function doRequest() {
var value = array[index];
InputGen(value['Type'], value['Name'], value['Other'], value['Value'], function(Html) {
Table = Table + "<tr><td>"+index+"</td><td>" + Html + "</td></tr>";
// This request is done, move to the next if any
if (++index < array.length) {
doRequest();
}
});
}
Side note: Overwhelmingly in JavaScript, variables and non-constructor functions are named starting with a lower-case letter: value rather than Value, etc. So I used index, array, and value above.
Side note 2: value['Type] can be more simply written as value.Type. (And so on.)
This is because AJAX is asynchronous. Nothing is going to wait for it to finish. The callback will run in the future at some point when the call is done. By then your $.each (and the code after) is long done.
The solution here is to use promises. That way you can run a callback once all the AJAX calls are done.
You can use jQuery's $.Deferred for this. Without editing the InputGen() function, you can do something like this:
var promises = [];
$.each(data['Result'][0], function(Key, Value) {
var d = new $.Deferred;
InputGen(Value['Type'], Value['Name'], Value['Other'], Value['Value'], function(Html){
d.resolve([Key, Html]);
});
promises.push(d.promise());
});
$.when.apply($, promises).done(function(){
for(var i=0, length=arguments.length; i < length; i++){
var ele = arguments[i],
Key = ele[0],
Html = ele[1];
Table = Table + "<tr><td>"+Key+"</td><td>" + Html + "</td></tr>";
}
// In here is where you can use your updated `Table` variable.
// You *cannot* use it outside of here, since it was not updated yet
});
I have three "sources," each of which needs to have an ajax call made. However, because Ajax is asynchronous, I can't just put it in a for loop. At the same time, I can't do async: false because it's bad to have the browser hang.
Thus, I decided to have the Ajax be called multiple times in it's success callback, and construct a kind of artificial loop. Problem is, it's not working properly (I'll explain the error later on in the question). Here's my relevant code.
counter: 0,
load: function(source, start_month, end_month, start_year, end_year) {
start_month = parseInt(start_month) + 1;
end_month = parseInt(end_month) + 1;
var parseDate = d3.time.format("%Y-%m-%d").parse;
if(source == 0) {
$.ajax({
dataType: "json",
url: ...
data: {
...
},
crossDomain: true,
success: function(raw_data) {
posts.counter++;
if(posts.counter < 4) {
alert(posts.counter);
posts.load(source, start_month, end_month, start_year, end_year);
} else {
alert("plot graph");
}
}
});
}
...
This entire code block exists inside a posts closure. Just a couple of questions:
Is this good style? Is there a more efficient way to go about doing this?
For some reason the alert is only firing twice... shouldn't it
be firing 3 times with 1, 2, and 3?
I suggest using JS promises (aka deferred objects). Look into jQuery's when and then functions (http://api.jquery.com/jquery.when/ and http://api.jquery.com/deferred.then/). You can use deferred objects to make 3 asynchronous calls and wait to process the data until all 3 calls return.
I have been working on a pretty big javascript function, and when I finally thought it was done, it stopped working completely.
Here it is:
function getLinksFrom(title) {
var returnArray = [],
plcontinue = '',
url = 'http://en.wikipedia.org/w/api.php?action=query&prop=links&titles=' + title + '&format=json&pllimit=500&plnamespace=0&callback=?';
while (returnArray.length === 0 || plcontinue !== '') {
if (plcontinue !== '') {
url = 'http://en.wikipedia.org/w/api.php?action=query&prop=links&titles=' + title + '&format=json&pllimit=500&plnamespace=0&plcontinue=' + plcontinue + '&callback=?';
}
$.ajax({url: url,
dataType: 'json',
success: function(data) {
console.log(data);
for (key in data['query']['pages']) {
links = data['query']['pages'][key]['links'];
}
for (var i = 0; i < links.length; i += 1) {
returnArray.push(links[i]['title']);
}
if (data.hasOwnProperty('query-continue')) {
plcontinue = data['query-continue']['links']['plcontinue'];
} else {
plcontinue = '';
}
}
});
}
return returnArray;
}
from what I can tell, it must be getting stuck in the while loop, as the page just freezes up. But with each loop, returnArray grows, and I'm testing it with something that won't set plcontinue to anything.
Any idea what is going wrong? Possibly something with asynchronous loading, I'm new at that.
EDIT: So I've figured out via helpful comments that it's looping again and again making more and more requests without waiting for the ajax to finish before starting again. How can I stop it from doing that?
The key is that AJAX is asynchronous. That means that when JS needs to perform request, it sends it and goes on, continuing to execute the rest of the code. When request is done, callback is fired.
So in your code it would works like that:
Initialization of variables
While loop condition check. Condition is true, so go to the loop body.
Executing ajax request. We send request and go on.
Again check condition. Since nothing changed (request haven't any time to complete), condition is still true.
And so on.
Eventually, maybe, one of dozen of request will end, modify returnArray and plcontinue, but this could take long time because what you have in your function body is, basically, infinite (almost) loop, which takes most of CPU resources.
The solution is to replace iteration with recursion: instead of iterating in infinite loop untill success, you can recurse until success. Delete while loop from getLinksFrom and add getLinksFrom() to the ajax request callback.
Another approack is to do that request synchronous. There is a special flag for it.
I'm creating a script that performs several functions and I want to update the user as the functions are completed. I have nested $.ajax() calls with each subsequent call in the previous call's success block.
There are a total of 4 calls made for each loop. Let's call them scan_1 through scan_4. The success block of scan_1 calls scan_2 and so on down the chain.
For example, let's say I'm looping over 3 objects. I want the process to go like this:
Loop 1
scan_1
scan_2
scan_3
scan_4
Loop 2
scan_1
scan_2
scan_3
scan_4
Loop 3
scan_1
scan_2
scan_3
scan_4
The problem is that it's running through all the scan_1 calls first. I must be missing something, but I can't seem to figure it out. Any advice would be much appreciated.
For reference, here is a snippet of scan_1 (irrelevant stuff snipped):
for(var i = 1; i <= 3; i++)
{
$.ajax({
type: 'GET',
url: url,
data: 'do=scan&step=1&' + string,
dataType: 'json',
success: function (result)
{
if(result.proceed == 'true')
{
$('#scan_progress').append(result.message);
scan_2();
}
else
{
$('#scan_progress').append(result.message);
}
}
});
}
Thoughts?
Thanks in advance.
Sounds like you need to use jQuery deferred. It basically allows you to chain multiple event handlers to the jQuery Ajax object and gives you finer control over when the callbacks are invoked.
Further reading:
http://msdn.microsoft.com/en-us/scriptjunkie/gg723713
http://www.erichynds.com/jquery/using-deferreds-in-jquery/
It's asynchronous - the "success" fires sometime in the future. The script does not wait for it to respond. Since you're firing off three requests in your loop, they will all be "scan1".
"scan_2" will be called as each request completes.
Change the request to synchronous if you want to control the order of events.
You are starting by sending off three ajax calls at once.
Scan1 (loop 1)
Scan1 (loop 2)
Scan1 (loop 3)
When each Scan 1 completes, it's subsequent Scan 2, and then Scan 3 are called.
What did you actually want to happen? Scan 1 2 and 3 of loop 1, then 1 2 and 3 of loop 2, and then 1 2 and 3 of loop 3? That would require more nesting, or possibly deferred objects.
Instead of using the success callback for each $.ajax() call, you can store each set of AJAX requests (their jqXHR objects) in an array and wait for all of them to resolve:
function scan_1 () {
//setup array to store jqXHR objects (deferred objects)
var jqXHRs = [];
for(var i = 1; i <= 3; i++)
{
//push a new index onto the array, `$.ajax()` returns an object that will resolve when the response is returned
jqXHRs[jqXHRs.length] = $.ajax({
type: 'GET',
url: url,
data: 'do=scan&step=1&' + string,
dataType: 'json'
});
}
//wait for all four of the AJAX requests to resolve before running `scan_2()`
$.when(jqXHRs).then(function () {
if(result.proceed == 'true') {
scan_2();
}
});
}
I've had similar problems working heavily with SharePoint web services - you often need to pull data from multiple sources to generate input for a single process.
To solve it I embedded this kind of functionality into my AJAX abstraction library. You can easily define a request which will trigger a set of handlers when complete. However each request can be defined with multiple http calls. Here's the component (and detailed documentation):
DPAJAX at DepressedPress.com
This simple example creates one request with three calls and then passes that information, in the call order, to a single handler:
// The handler function
function AddUp(Nums) { alert(Nums[1] + Nums[2] + Nums[3]) };
// Create the pool
myPool = DP_AJAX.createPool();
// Create the request
myRequest = DP_AJAX.createRequest(AddUp);
// Add the calls to the request
myRequest.addCall("GET", "http://www.mysite.com/Add.htm", [5,10]);
myRequest.addCall("GET", "http://www.mysite.com/Add.htm", [4,6]);
myRequest.addCall("GET", "http://www.mysite.com/Add.htm", [7,13]);
// Add the request to the pool
myPool.addRequest(myRequest);
Note that unlike many of the other solutions provided this method does not force single threading of the calls being made - each will still run as quickly (or as slowly) as the environment allows but the single handler will only be called when all are complete. It also supports the setting of timeout values and retry attempts if your service is a little flakey.
I've found it insanely useful (and incredibly simple to understand from a code perspective). No more chaining, no more counting calls and saving output. Just "set it and forget it".