1252748
1252748

Reputation: 15372

ajax success callback executes out of expected order

I have a function that validates some fields when a button is pressed. First one checks if the username and password are authentic or not. then if the password is appropriately secure. Then if it matches the confirm-password box. However, it seems that the ajax that check to see if the user is authentic does not complete before the alert in the first function pops up.

$(document).ready(function() {
    $('#submit_pw_ch').on('click', function(){

        var alertString = "Please correct the following errors: \n"
        //current username and password are correct
        var vUsr = valUsr();
        //new password is sufficiently secure
        var vPwd = valPwd();
        //confirmation of new password is same as previous box
        var sPwd = samePwd();

        console.log('valid user : ' + vUsr + '      valid password : ' + vPwd + '     same password : ' + sPwd);

        //append appropriate warnings to alert string
        if ( !vUsr ) { alertString += "-Your current username and password are invalid. \n"; }
        if ( !vPwd ) { alertString += "-The new password you have selected is not strong enough. \n"; }
        if ( !sPwd ) { alertString += "-The confirmation of your new password does not match the previous entry. \n"; }


        if ( !vUsr || !vPwd || !sPwd ) {
            alert(alertString);
            return false;
        } else {

            //change password

        }

    });
});

So the line that checks for that is var vUsr = valUsr(); which calls

function valUsr() {

    var un = $('#uNameInput').val();
    var pw = $('#uPwdInput').val();
    //return value
    var validUsr = false;

    $.ajax({
        type: "post",
        url: "queries/confirm_user.php?<?=time()?>",
        data: "un=" + un + "&pw=" + pw,
        dataType: "json",
        success: function (returnedData) {
            console.log(returnedData)
            if (data == 'true') {
                validUsr = true;
            } else {
                validUsr = false;
            }
        } 

    });

    return validUsr;
}

Somehow though the alert is not waiting for the ajax to finish getting it's data. The console.log(returnedData) in the valUsr() function appears in the console after I've dismissed the alert box. Why is this happening? How can I prevent it? Thanks!

Upvotes: 0

Views: 469

Answers (2)

lededje
lededje

Reputation: 1869

Ajax is run on the fly, synchronously

You will need to check the validation of the other fields after the ajax request has completed in the success callback. You can turn off the synchronous request but the browser will freeze up 'till it gets one, not advised.

You will need to restructure your calls to reflect this; I would suggest that as soon as they have finished typing the password and the field blurs you send the request to check. That way, if there are any errors you will be able to prevent the wait time at the end of the form.

Upvotes: 1

Beetroot-Beetroot
Beetroot-Beetroot

Reputation: 18078

Thomas,

You need to cater for the inherent asynchronicity of ajax, in other words you need to wait until a response to the ajax request has arrived before deciding what to do.

jQuery's Deferreds (and promises) allow us to write simple code but Deferreds tend to blow you mind at first, at least very slightly.

There's no unique way in which to use Deferreds for a problem like this but here's one.

$(document).ready(function() {
    $('#submit_pw_ch').on('click', function() {
        var form = this.form; //the form containing the submit button and the fields .

        //`alertArr` is an array to which messages will be added.
        var alertArr = ["Please correct the following errors:"];

        //`addAlert` will be called from a `Deferred.resolveWith(this, ...)` statement.
        //The context, `this`, is unused.
        function addAlert(index, txt) {
            alertArr[index] = txt;
        }

        //`compositeAction` will be called from a promise.done() statement.
        function compositeAction() {
            //first filter out null messages (ie. validation successes) from alertArr.
            var message = $.map(alertArr, function(txt, i){
                return txt || null;
            });
            if(message.length > 1) {
                //then alert the complete message with line breaks
                alert(message.join("\n"));
            } else {
                //submit the form to change the password
                //or another ajax call as required
                form.submit();
            }
        }

        // Invoke ajax validators and assign returned promises.
        // An index is passed, so the text messages can be displayed in a logical order, 
        // regardless of the order in which the validation promises are resolved.
        //If we didn't care about the order of the messages then the code would be slighly simpler.
        var vUsr = valUsr(0),
            vPwd = valPwd(1),
            sPwd = samePwd(2);

        //All validations adopt a similar pattern regardless of whether ajax is involved or not.
        //Here, we establish what is to be done when the promise are resolved, or
        //what is to be done immediately if the promise are alrady resolved.
        vUsr.done(addAlert);
        vPwd.done(addAlert);
        sPwd.done(addAlert);
        //At this stage, `addAlert` will contain entries for successful as well as unsuccessful validations. Successful entries will be filtered out by `compositeAction`

        //Establish what is to be done when all three promises are resolved.
        $.when(vUsr, vPwd, sPwd).done(compositeAction);

        //Return false unconditionally
        return false;
    });

    function valUsr(index) {
        var messages = {
            validated: '',//important - this message must be an empty string - do not change
            notValidated: '- Your current username and password are invalid.',
            ajaxError: '- Validation error: username and password.'
        };

        //Create a Deferred object, which will be resolved in response to several different outcomes.
        var def = $.Deferred();

        $.ajax({
            type: "post",
            url: "queries/confirm_user.php?<?=time()?>",
            data: {
                'un': $('#uNameInput').val(),
                'pw': $('#uPwdInput').val()
            },
            dataType: "json",
            success: function (returnedData) {
                if (returnedData == 'true') {
                    def.resolveWith(this, [index, messages.validated]);
                } else {
                    def.resolveWith(this, [index, messages.notValidated]);
                }
            },
            error: function() {
                def.resolveWith(this, [index, messages.ajaxError]);
            }
        });
        return def.promise();//Return a promise derived from the as yet unresolved Deferred.
    }

    function samePwd(index) {
        var pw1 = $('#uPwdInput').val();
        var pw2 = $('#uPwdInput2').val();
        var errMessage = (pw1 === pw2) ? '' : '-The confirmation of your new password does not match the previous entry';
        var def = $.Deferred();//Create a Deferred object, which will be resolved immediately
        def.resolveWith(this, [index, errMessage]);
        return def.promise();//Return a promise derived from the already resolved Deferred.
    }
});

valPwd() will be of the same format as either valUsr() or samePwd(), depending on whether ajax is involved or not.

Upvotes: 2

Related Questions