AJAX似乎比我的其余代码移动得更快

I have a "forgot password" script that allows users to reset their password after receiving an email. For some reason, I cannot get it to consistently work. It works about 70% of the time, but sometimes it still gives incorrect information.

The user is supposed to type in their username, then create a new password and then verify the new password. The username is compared to a salted and hashed username from a PHP file. If the typed username matches the salted and hashed username, then there is a checkmark placed next to the input box of the username.

The only way this works 100% of the time is when the users enter their names really slowly. I am just assuming this is a common AJAX/JavaScript/jQuery problem, but I could be wrong. If the users type their names in slowly, then when the last letter is entered, they get a check mark. I can replicate this issue when the users type their names in quickly. 3 out of 10 times after they get to the last letter, even though they will type in the correct username, it will not recognize it as correct. If they erase the last letter, then type it in again (the same exact last letter), it will then recognize this as the correct username.

My JavaScript is below:

$( document).ready( function() {
  var $usernameCheck = false;
  var $newPass       = false;
  var $rNewPass      = false;

  $( '#user-name' ).keyup(function() {
    $.get(
        'forgot-compare.php',
        { 
            compare1 : $( '#user-name' ).val(),
            compare2 : $( '#forgotLink').val()
        },
        function(data) {
            if ( data == "1" ) {
                $usernameCheck = true;
                $( '#u-check' ).removeClass().addClass( 'fa fa-check fa-lg check-green' );
            }
            else if (data == "2") {
                $usernameCheck = false;
                $( '#u-check' ).removeClass().addClass( 'fa fa-times fa-lg check-red' );
            }
            submitCheck();
        }
    );

    setTimeout( submitCheck(), 3000);

});

$( '#new-pass' ).keyup( function() {
    var $newPword = $( '#new-pass' ).val().length;

    if ( $newPword >= 10 ) {
        $newPass = true;
        $( '#p-check' ).removeClass().addClass( 'fa fa-check fa-lg check-green' );
    }
    else if ( $newPword == 0 ) {
        $newPass = false;
        $( '#p-check' ).removeClass();
    }
    else if ( $newPword < 10 ) {
        $newPass = false;
        $( '#p-check' ).removeClass().addClass( 'fa fa-times fa-lg check-red' );
    }

    submitCheck();
});

$( '#retype-new-pass' ).keyup( function() {
    var $rNewPword = $( '#retype-new-pass' ).val();

    if ( $rNewPword == $( '#new-pass' ).val() ) {
        $rNewPass = true;
        $( '#r-check' ).removeClass().addClass( 'fa fa-check fa-lg check-green' );
    }
    else if ( $rNewPword != $( '#new-pass' ).val() ) {
        $rNewPass = false;
        $( '#r-check' ).removeClass().addClass( 'fa fa-times fa-lg check-red' );
    }

    submitCheck();
});

var submitCheck = function() {
    if ($usernameCheck === true &&
        $newPass       === true &&
        $rNewPass      === true
       ) {
        $('#pSubmit').removeProp('disabled');
    }
    else if ($usernameCheck === false ||
             $newPass       === false ||
             $rNewPass      === false
            ) {
        $('#pSubmit').prop('disabled', true);
    }
  }
});

Once their username is correct, their new password is at least 10 characters, and their re-typed password matches their new password, then the submit button on the page is enabled. Otherwise, the submit button is disabled.

I'd put my PHP and HTML in this post as well (I still will if someone would like), but I can completely replicate the problem by typing in the username quickly, and also "solve" the problem by typing it in slowly.

It seems like when the user types their username in quickly, the AJAX call is "tripping over itself" and getting confused with what to assign the $usernameCheck variable from the last key input. I've run into this before on smaller scale projects but haven't really paid it any mind. Now it seems to be an issue in this bigger project.

What I've tried: -setTimeout in various places in the script, to delay when the submitCheck() function is run. Didn't work. -.delay() n various places in the script, for the same purpose as above. Didn't work.

What approach should I take to solving this? Is this a common problem, and what is the general solution to this? Is there a way that I can wait to see if anymore keystrokes were pressed before running a function? (Maybe, if no keys were pressed in the last 500 milliseconds, THEN run the JS function)

Consider this: I have added a check so that you wait .5 seconds (you can play with this value) after every key-up. And then if the user presses a button during these .5 seconds, your timeout will be reset, preventing multiple submissions within that time frame.

$( document).ready( function() {
    var $usernameCheck = false;
    var $newPass       = false;
    var $rNewPass      = false;
    var myTimeout = null;

    var checkUsername = function(){
        $.get(
            'forgot-compare.php',
            { 
                compare1 : $( '#user-name' ).val(),
                compare2 : $( '#forgotLink').val()
            },
            function(data) {
                if ( data == "1" ) {
                    $usernameCheck = true;
                    $( '#u-check' ).removeClass().addClass( 'fa fa-check fa-lg check-green' );
                }
                else if (data == "2") {
                    $usernameCheck = false;
                    $( '#u-check' ).removeClass().addClass( 'fa fa-times fa-lg check-red' );
                }
                submitCheck();
            }
        );
    }

    $( '#user-name' ).keyup({
        clearTimeout(myTimeout);
        myTimeout = setTimeout(checkUsername, 500);
    });

});

Alternatively, you should consider canceling your previous ajax request. Here is one way to do it:

$( document).ready( function() {
    var $usernameCheck = false;
    var $newPass       = false;
    var $rNewPass      = false;
    var ajaxRequest = null;

    var checkUsername = function(){
        if(ajaxRequest !== null){
            ajaxRequest.abort();
        }
        ajaxRequest = $.get(
            'forgot-compare.php',
            { 
                compare1 : $( '#user-name' ).val(),
                compare2 : $( '#forgotLink').val()
            },
            function(data) {
                if ( data == "1" ) {
                    $usernameCheck = true;
                    $( '#u-check' ).removeClass().addClass( 'fa fa-check fa-lg check-green' );
                }
                else if (data == "2") {
                    $usernameCheck = false;
                    $( '#u-check' ).removeClass().addClass( 'fa fa-times fa-lg check-red' );
                }
                submitCheck();
            }
        ).always(function() {
            ajaxRequest = null;
        });
    }

    $( '#user-name' ).keyup({
        checkUsername();
    });
});

And, of course, you can incorporate both the timeout and the canceling of the ajax request within your code.

As @adam said, your ajax callbacks may not be in order. You can solve this in two ways:

  1. skip some ajax requests: use something like underscore throttle

  2. skip some ajax callbacks: disable previous callbacks once a new ajax request is made