8
votes

Let me explain my code a little bit (Excuse me if somethings wrong, I've just written this example from scratch, it is very close to what I currently have).

HTML:

<form id="form">
    <!-- Friend 1 -->
    Name 1: <input type="text" name="friendName1" id="friendName1" class="friendName" value=""><br />
    Email 1: <input type="text" name="friendEmail1" id="friendEmail1" value=""><br /><br />
    <!-- Friend 2 -->
    Name 2:<input type="text" name="friendName2" id="friendName2" class="friendName" value=""><br />
    Email 2:<input type="text" name="friendEmail2" id="friendEmail2" value=""><br /><br />
    <!-- Friend 3 -->
    Name 3:<input type="text" name="friendName3" id="friendName3" class="friendName" value=""><br />
    Email 3:<input type="text" name="friendEmail3" id="friendEmail3" value=""><br /><br />
    <!-- Friend 4 -->
    Name 4:<input type="text" name="friendName4" id="friendName4" class="friendName" value=""><br />
    Email 4:<input type="text" name="friendEmail4" id="friendEmail4" value=""><br /><br />
    <!-- Submit -->
    <input name="submit" type="submit" value="Submit">
</form>

JS:

$("#form").submit(function(){

    $(".friendName[value!='']").each(function(){
        var idEmail = 'friendEmail' + $(this).attr("id").replace('friendName','');
        if($("#"+idEmail+"[value!='']").length > 0){
            var name = $(this).val();
            var email = $("#"+idEmail).val();

            // Submit the ajax request
            $.ajax({ 
                type: 'POST', 
                url: 'ajax/url', 
                data: {
                    name: name,
                    email: email
                },
                success: function(json) {
                    // Log a console entry if our ajax request was successful
                    console.log(name + " was submitted via ajax");
                }
            });
        }
    });

    // Redirect the user to the thank you page
    setTimeout( function() { window.location= '/thanks'; }, 2000 );

});

JSFiddle (redirect removed and ajax call replaced with console log for fiddle)

http://jsfiddle.net/cM5PX/

The HTML is a simple form, with friend name and friend email input fields.

The JS has an each function, which if the name and associated email have values, it runs an ajax call.

I need a way for these ajax calls to run (there could be 1 loop, there could be 15) and then after they have all finished, redirect to a new page.

The current way I'm doing it is horrible, as all of the ajax calls do not finish running before the page redirects.

What can I do to make this better? It needs to be fool proof and ONLY redirect once all of the ajax calls have finished (success or error, it doesn't matter - it just needs to redirect once its finished).

I have tried using async: false but it doesn't seem to make a difference.

I've thought about putting a counter in the each function and a counter in the ajax success function and if they both match, then do the redirect, but I am looking for some more experienced guidance.

4

4 Answers

28
votes

Use deferred objects:

$("#form").submit(function(e){

    e.preventDefault();

    var calls = [];

    $(".friendName[value!='']").each(function(){
        // Submit the ajax request
        calls.push($.ajax({ 
            type: 'POST', 
            url: 'ajax/url', 
            data: {
                name: name,
                email: email
             },
             success: function(json) {
                // Log a console entry if our ajax request was successful
                console.log(name + " was submitted via ajax");
             }
         }));
    });

    $.when.apply($, calls).then(function() {
        window.location= '/thanks';
    });
});
6
votes

Ok, this is fairly easy since you're using jQuery. jQuery comes with an integrated promise maker which also is wired up with jQuery's Ajax calls. What does that mean? Well, we can easily go like this:

var requests = [ ];

// ...

// Submit the ajax request
requests.push($.ajax({ 
    type: 'POST', 
    url: 'ajax/url', 
    data: {
        name: name,
        email: email
    },
    success: function(json) {
        // Log a console entry if our ajax request was successful
        console.log(name + " was submitted via ajax");
    }
}));

// at this point we filled our array with jXHR objects which also inherit the promise API

$.when.apply( null, requests ).done(function() {
    document.location.href = '/thanks';
});

Note: The above code will only fire, if all requests completed successfully. If you need to handle the case if one ore more requested failed, use .then() or .fail() instead of .done(), too.

2
votes

Just keep a counter of the AJAX calls and check to see when they have all completed. Something like this:

$("#form").submit(function(){
    var ajaxMax = $(".friendName[value!='']").length, ajaxCounter = 0;
    $(".friendName[value!='']").each(function(){
        var idEmail = 'friendEmail' + $(this).attr("id").replace('friendName','');
        if($("#"+idEmail+"[value!='']").length > 0){
            var name = $(this).val();
            var email = $("#"+idEmail).val();

            // Submit the ajax request
            $.ajax({ 
                type: 'POST', 
                url: 'ajax/url', 
                data: {
                    name: name,
                    email: email
                },
                success: function(json) {
                    // Log a console entry if our ajax request was successful
                    console.log(name + " was submitted via ajax");
                    if(++ajaxCounter >= ajaxMax)
                        window.location= '/thanks';
                }
            });
        }
    });
});
0
votes

By default $.ajax is asynchronous. In the options hash being passed, add

async: false

That will serialize the calls you are making having it perform the way you want.


After getting comment from original poster, possible solution may be to do the following:

  • Inside the submit call determine the number of calls that should be made.
  • Store this result as a local variable within the handler function
  • Create a callback on 'complete:' that will examine the value of the calls
    • If the number of calls to be made is greater than 0, then decrement the value and return
    • If the number of calls reaches zero, update the window.location (really should use window.location.href as window.location is an object but browsers will allow this and perform correctly)

Note, I don't have any information on the thread safety of performing this kind of operation so YMMV.


Example Code:

$("#form").submit(function(eventObject){
    var $ajaxFields= $(".friendName[value!='']").filter(function(index){
        var idEmail = 'friendEmail' + $(this).attr("id").replace('friendName','');
        return ($("#"+idEmail+"[value!='']").length > 0);
    });

    var numberToSubmit= $ajaxFields.length;
    $ajaxFields.each(function(){
        var idEmail = 'friendEmail' + $(this).attr("id").replace('friendName','');
        var name = $(this).val();
        var email = $("#"+idEmail).val();

        // Submit the ajax request
        $.ajax({ 
            type: 'POST', 
            url: 'ajax/url', 
            data: {
                name: name,
                email: email
            },
            success: function(json) {
                // Log a console entry if our ajax request was successful
                console.log(name + " was submitted via ajax");
            },
            error: function(jqXHR, textStatus, errorThrown) {
                console.log("call not completed: "+textStatus);
            },
            complete: function(jqXHR, textStatus) {
                if( --numberToSubmit == 0 ) {
                    window.location.href= '/thanks';
                }
            }
        });
    });

    // Prevent default form submission
    return false;
});