1

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);

});
hippietrail
  • 15,848
  • 18
  • 99
  • 158
Joey Aron
  • 11
  • 1

2 Answers2

1

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.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • I beg to differ. `copyUser` & `moveUser` are not Deferreds themselves. Once called, they'll return a Deferred. The only way for `when`/`then` to work is if you call the function, and pass the returned Deferred into `when`/`then`. – Joseph Silber Dec 26 '11 at 22:36
  • Also, he absolutely *must* use the `pipe` method. If you're just adding `then` functions, they won't wait for one another. The OP clearly wants `moveUser` to return before he runs `cleanup`. The only way to accomplish that is through `pipe`. – Joseph Silber Dec 26 '11 at 22:39
  • 1
    OK, switched back to `pipe()`. The jQuery doc on pipe is worthless. – jfriend00 Dec 26 '11 at 22:47
  • @JosephSilber - calling `moveUser()` initiates the ajax function immediately. It can't be deferred if you execute it right away. `.then(fn)` takes a callback or list of callbacks, not a deferred. – jfriend00 Dec 26 '11 at 22:51
  • You're partially correct (and I was partially wrong). `when` takes a Deferred, but `pipe` takes a callback. – Joseph Silber Dec 26 '11 at 22:56
  • For a nice overview/use-case of `pipe` check out [Ben Nadel's excellent post](http://www.bennadel.com/blog/2289-Using-jQuery-s-Pipe-Method-To-Chain-Asynchronous-Validation-Requests.htm). – Joseph Silber Dec 26 '11 at 22:58
  • @JosephSilber - OK, it looks like we both fixed ours. I'm answering this question mostly to force myself to try to understand deferreds better. Thanks for your help. Honestly, I think my non-deferred solution is simpler to understand. – jfriend00 Dec 26 '11 at 22:58
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/6148/discussion-between-joseph-silber-and-jfriend00) – Joseph Silber Dec 26 '11 at 22:58
  • BTW, here's a great article on the subject: [Understanding JQuery.Deferred and Promise](http://joseoncode.com/2011/09/26/a-walkthrough-jquery-deferred-and-promise/) – Joseph Silber Jan 02 '12 at 20:48
1

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);
Joseph Silber
  • 214,931
  • 59
  • 362
  • 292
  • But won't this still fire all the `copyUser()` functions at once as the .each() loop will just run to it's completion without waiting for anything? – jfriend00 Dec 26 '11 at 22:21
  • Yes it will. I hadn't realized that the OP minded the loop running async. Will update my answer in a minute. – Joseph Silber Dec 26 '11 at 22:29
  • Isn't this line `$.when( copyUser($items.eq(i).val()) ).pipe( moveUser() )` going to call `copyUser()` and `moveUser()` immediately? and pass their return value to the `$.when()` and `.pipe()` methods. I don't think that's what the OP wants. It won't sequence the ajax calls. – jfriend00 Dec 26 '11 at 22:48
  • @jfriend00 - You're right. `moveUser` should be passed in to `then` as a reference, but `copyUser` should be executed right away, so that it's Deferred gets passed in to `when` (just like the OP has done it). I updated my answer... – Joseph Silber Dec 26 '11 at 22:54