Execute a set of work in a loop only after the previous set has completed

631 Views Asked by At

I have some code that completes a distinct set of operations in a chain. The set of operations, itself, is executed in a loop. Because of the nature of the system I'm dealing with, the operation sets need to be executed synchronously (i.e., the next set of operations cannot execute before the first set is finished). Asynchronous execution can throw off the remote processor (not in my control), and cause the code to fail. Right now, the only way I've been able to get this to work is to use alert boxes, which is truly lame and not an option. I've tried:

  • using $.ajax, and setting the async to false
  • using a sleep function
  • alert boxes (used in desperation)

Here is the code - Any thoughts?:

$('input:checked').each(function(index){                        

    //get the item's location id
    var currentInput = $(this).val();

    var copyUser = function(){$.get(urlCopyPrefix + currentInput + urlSuffix);}
    var moveUser = function(){$.get(urlMovePrefix + moveLocation + urlSuffix);}

    var cleanup = function(){

        var newSibling = $('#module-' + moveLocation);
        newSibling.before('<li>New Line</li>');

        alert('done');
    }

    /* --> THIS IS THE CODE IN QUESTION <-- */
    $.when(copyUser()).pipe(moveUser).then(cleanup);

});
2

There are 2 best solutions below

9
On

I think you'll have to stop using .each() because it runs the whole iteration right away and you don't want to do that.

Here's one way to do it, using completion functions and a local function:

function copyMove(moveLocation, urlCopyPrefix, urlMovePrefix, urlSuffix)
    var checked = $('input:checked');
    var index = 0;

    function next() {
        if (index < checked.length) {
            var currentInput = checked.eq(index++).val();
            $.get(urlCopyPrefix + currentInput + urlSuffix, function() {
                $.get(urlMovePrefix + moveLocation + urlSuffix, function() {
                    $('#module-' + moveLocation).before('<li>New Line</li>');
                    next();
                });
            });
         }
    }

    next();
}

Or, using deferreds:

function copyMove(moveLocation, urlCopyPrefix, urlMovePrefix, urlSuffix)
    var checked = $('input:checked');
    var index = 0;

    function copyUser () { 
        var currentInput = checked.eq(index).val();
        return $.get(urlCopyPrefix + currentInput + urlSuffix);
    }
    function moveUser() {
        return $.get(urlMovePrefix + moveLocation + urlSuffix);
    }
    function cleanup() {
        ++index;
        $('#module-' + moveLocation).before('<li>New Line</li>');
        next();
    }

    function next() {
        if (index < checked.length) {
            $.when(copyUser()).pipe(moveUser).then(cleanup);
         }
    }

    next();
}

A note on your deferreds: You have to pass function references, not function calls. That means you pass the function name without parens.

4
On

You have to return the jqXHR object from your functions:

var copyUser = function(){
        return $.get(urlCopyPrefix + currentInput + urlSuffix);
    },
    moveUser = function(){
        return $.get(urlMovePrefix + moveLocation + urlSuffix);
    };

Then you can do:

$.when( copyUser() ).pipe( moveUser ).then( cleanup );

If you want to wait for each item in the loop to be done before the next one starts, use this:

var $items = $('input:checked'),
    length = $items.length,
    copyUser = function(currentInput)
    {
        return $.get(urlCopyPrefix + currentInput + urlSuffix);
    },
    moveUser = function()
    {
        return $.get(urlMovePrefix + moveLocation + urlSuffix);
    },
    cleanup = function()
    {
        $('#module-' + moveLocation).before('<li>New Line</li>');
    };

function processUser(i)
{
    $.when( copyUser($items.eq(i).val()) ).pipe( moveUser ).then(function()
    {
        cleanup();
        i < length && processUser(++i);
    });
}

processUser(0);