Chris J Chris J - 2 months ago 6
Javascript Question

Failing to delay function until after 'for' loop has fully completed

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

Answer

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!");
});
Comments