0

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

RiggsFolly
  • 93,638
  • 21
  • 103
  • 149
Chris J
  • 1,441
  • 9
  • 19
  • 1
    Have a look at: [Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference](http://stackoverflow.com/questions/23667086/why-is-my-variable-unaltered-after-i-modify-it-inside-of-a-function-asynchron) – Andreas Sep 20 '16 at 10:54
  • 1
    Use promise.. Or for simple ajax, use Callback. – Tirthraj Barot Sep 20 '16 at 11:02
  • You can use recursive call to the function into the success parameter of $.ajax. This will load everything in the exact order you want them to. – Tirthraj Barot Sep 20 '16 at 11:03
  • Please notify me if you want any answer or clarification about my comment. I ll certainly help you out. – Tirthraj Barot Sep 20 '16 at 11:03
  • Thanks for the comments. Whilst I understand asynchronous functions, I'm struggling to get my head around it in this context. I've learnt C and php long before JS, so I think I am used to things being (almost) completely sequential. – Chris J Sep 20 '16 at 11:13
  • Is this PHP code (`$i` ?) mixed into JS ? – Alnitak Sep 20 '16 at 11:38
  • Yes, also just clarified in one of the answers you commented on. The ` – Chris J Sep 20 '16 at 11:49

3 Answers3

1

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!");
});
Alnitak
  • 334,560
  • 70
  • 407
  • 495
  • Brilliant. Works exactly to plan. I'll have to study it to really understand how this is done, but it looks like a fantastic solution. Thanks! – Chris J Sep 20 '16 at 11:43
  • @ChrisJ the real "magic" is in that `loop()` function, which repeatedly calls itself the required number of times, and also ensures that the AJAX calls don't all proceed in parallel. – Alnitak Sep 20 '16 at 12:15
0

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.

  1. 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.
  2. 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.

  3. 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.

Community
  • 1
  • 1
Tirthraj Barot
  • 2,671
  • 2
  • 17
  • 33
  • The reason why I have split the requests into chunks is that each chunk handles around 2500 rows in a DB Table. When this is performed in a single request, it results in huge issues with memory and server performance, as one single request can result in 250,000+ of result rows. The AJAX is quite quick and functional, but just has teething issues. Does this help clarify why I have done it this way? – Chris J Sep 20 '16 at 11:16
  • It certainly does. ! @ChrisJ – Tirthraj Barot Sep 20 '16 at 11:21
  • 1
    but the .promise() system would certainly be good for such application. http://www.htmlgoodies.com/beyond/javascript/making-promises-with-jquery-deferred.html – Tirthraj Barot Sep 20 '16 at 11:23
-1

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');
}
jaboja
  • 2,178
  • 1
  • 21
  • 35
  • Unfortunately, this still doesn't work. The 'test' alert will appear randomly within the results set. Tested on an 8-iteration process, it set off the alert at a random point before the end of the loop – Chris J Sep 20 '16 at 11:26
  • this is incorrect code - the `i` inside the loop is not correctly bound to the current iteration count when it's used in the `success` handler. – Alnitak Sep 20 '16 at 11:37
  • That could be my fault for using php `$i` as a parent iteration, and JS `i` as my child iteration, which has probably made it look like they are one and the same in this context, throwing off the author of the answer – Chris J Sep 20 '16 at 11:46
  • @ChrisJ ah, yes, you're right! I've only used `i` in my answer, too. – Alnitak Sep 20 '16 at 12:19
  • I amended that before using. The first 'i' is okay, as I retrieve it via `GET` on the other side for determining result offset, it's just the second one I declare as `$i` for my variable variable – Chris J Sep 20 '16 at 14:48