diff --git a/src/ajax.js b/src/ajax.js index fb56de4b3..5f87bed20 100644 --- a/src/ajax.js +++ b/src/ajax.js @@ -1,5 +1,6 @@ // Ajax mode: abort // usage: $.ajax({ mode: "abort"[, port: "uniqueport"]}); +// $.ajaxAbort( port ); // if mode:"abort" is used, the previous request on that port (port can be undefined) is aborted via XMLHttpRequest.abort() var pendingRequests = {}, @@ -10,9 +11,7 @@ if ( $.ajaxPrefilter ) { $.ajaxPrefilter( function( settings, _, xhr ) { var port = settings.port; if ( settings.mode === "abort" ) { - if ( pendingRequests[ port ] ) { - pendingRequests[ port ].abort(); - } + $.ajaxAbort( port ); pendingRequests[ port ] = xhr; } } ); @@ -24,12 +23,18 @@ if ( $.ajaxPrefilter ) { var mode = ( "mode" in settings ? settings : $.ajaxSettings ).mode, port = ( "port" in settings ? settings : $.ajaxSettings ).port; if ( mode === "abort" ) { - if ( pendingRequests[ port ] ) { - pendingRequests[ port ].abort(); - } + $.ajaxAbort( port ); pendingRequests[ port ] = ajax.apply( this, arguments ); return pendingRequests[ port ]; } return ajax.apply( this, arguments ); }; } + +// Abort the previous request without sending a new one +$.ajaxAbort = function( port ) { + if ( pendingRequests[ port ] ) { + pendingRequests[ port ].abort(); + delete pendingRequests[ port ]; + } +}; diff --git a/src/core.js b/src/core.js index ddb258119..923b3012b 100644 --- a/src/core.js +++ b/src/core.js @@ -756,6 +756,9 @@ $.extend( $.validator, { val = this.elementValue( element ), result, method, rule, normalizer; + // Abort any pending Ajax request from a previous call to this method. + this.abortRequest( element ); + // Prioritize the local normalizer defined for this element over the global one // if the former exists, otherwise user the global one in case it exists. if ( typeof rules.normalizer === "function" ) { @@ -1095,6 +1098,10 @@ $.extend( $.validator, { return !$.validator.methods.required.call( this, val, element ) && "dependency-mismatch"; }, + elementAjaxPort: function( element ) { + return "validate" + element.name; + }, + startRequest: function( element ) { if ( !this.pending[ element.name ] ) { this.pendingRequest++; @@ -1130,6 +1137,24 @@ $.extend( $.validator, { } }, + abortRequest: function( element ) { + var port; + + if ( this.pending[ element.name ] ) { + port = this.elementAjaxPort( element ); + $.ajaxAbort( port ); + + this.pendingRequest--; + + // Sometimes synchronization fails, make sure pendingRequest is never < 0 + if ( this.pendingRequest < 0 ) { + this.pendingRequest = 0; + } + + delete this.pending[ element.name ]; + } + }, + previousValue: function( element, method ) { method = typeof method === "string" && method || "remote"; @@ -1570,7 +1595,7 @@ $.extend( $.validator, { data[ element.name ] = value; $.ajax( $.extend( true, { mode: "abort", - port: "validate" + element.name, + port: this.elementAjaxPort( element ), dataType: "json", data: data, context: validator.currentForm, diff --git a/test/methods.js b/test/methods.js index fa49939e4..2966ec25f 100644 --- a/test/methods.js +++ b/test/methods.js @@ -801,6 +801,36 @@ QUnit.test( "Fix #697: remote validation uses wrong error messages", function( a } ); } ); +QUnit.test( "Fix #2434: race condition in remote validation rules", function( assert ) { + var e = $( "#username" ), + done1 = assert.async(), + v = $( "#userForm" ).validate( { + rules: { + username: { + required: true, + remote: { + url: "users.php" + } + } + }, + messages: { + username: { + remote: $.validator.format( "{0} in use" ) + } + } + } ); + + e.val( "Peter" ); + v.element( e ); + + e.val( "" ); + v.element( e ); + setTimeout( function() { + assert.equal( v.errorList[ 0 ].message, "This field is required." ); + done1(); + } ); +} ); + QUnit.module( "additional methods" ); QUnit.test( "phone (us)", function( assert ) {
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: