Re-organizes ajax.js: prefilters and transports are no longer stored in ajaxSettings (their structure is not handled correctly by extend() and was causing some overhead when constructing the final options map in ajax()); base function for ajaxPrefilter and ajaxTransport has been renamed and split in two (one for inspection, one for addition); response/dataType determination and data conversion logics have been externalized from the ajax() internal callback; data conversion no longer sets responseXXX fields; some minor re-formatting and simplifications.

This commit is contained in:
jaubourg 2011-01-22 04:45:20 +01:00
parent 4413c2fd93
commit bea4815294

View file

@ -2,6 +2,7 @@
var r20 = /%20/g, var r20 = /%20/g,
rbracket = /\[\]$/, rbracket = /\[\]$/,
rCRLF = /\r?\n/g,
rhash = /#.*$/, rhash = /#.*$/,
rheaders = /^(.*?):\s*(.*?)\r?$/mg, // IE leaves an \r character at EOL rheaders = /^(.*?):\s*(.*?)\r?$/mg, // IE leaves an \r character at EOL
rinput = /^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i, rinput = /^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,
@ -9,15 +10,31 @@ var r20 = /%20/g,
rquery = /\?/, rquery = /\?/,
rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
rselectTextarea = /^(?:select|textarea)/i, rselectTextarea = /^(?:select|textarea)/i,
rspacesAjax = /\s+/,
rts = /([?&])_=[^&]*/, rts = /([?&])_=[^&]*/,
rurl = /^(\w+:)?\/\/([^\/?#:]+)(?::(\d+))?/, rurl = /^(\w+:)?\/\/([^\/?#:]+)(?::(\d+))?/,
rCRLF = /\r?\n/g,
// Slice function // Slice function
sliceFunc = Array.prototype.slice, sliceFunc = Array.prototype.slice,
// Keep a copy of the old load method // Keep a copy of the old load method
_load = jQuery.fn.load; _load = jQuery.fn.load,
// Prefilters
// 1) They are useful to introduce custom dataTypes (see transport/jsonp for an example)
// 2) These are called:
// * BEFORE asking for a transport
// * AFTER param serialization (s.data is a string if s.processData is true)
// 3) key is the dataType
// 4) the catchall symbol "*" can be used
// 5) execution will start with transport dataType and THEN continue down to "*" if needed
prefilters = {},
// Transports bindings
// 1) key is the dataType
// 2) the catchall symbol "*" can be used
// 3) selection will start with transport dataType and THEN go to "*" if needed
transports = {};
jQuery.fn.extend({ jQuery.fn.extend({
load: function( url, params, callback ) { load: function( url, params, callback ) {
@ -204,22 +221,6 @@ jQuery.extend({
text: "responseText" text: "responseText"
}, },
// Prefilters
// 1) They are useful to introduce custom dataTypes (see transport/jsonp for an example)
// 2) These are called:
// * BEFORE asking for a transport
// * AFTER param serialization (s.data is a string if s.processData is true)
// 3) key is the dataType
// 4) the catchall symbol "*" can be used
// 5) execution will start with transport dataType and THEN continue down to "*" if needed
prefilters: {},
// Transports bindings
// 1) key is the dataType
// 2) the catchall symbol "*" can be used
// 3) selection will start with transport dataType and THEN go to "*" if needed
transports: {},
// List of data converters // List of data converters
// 1) key format is "source_type destination_type" (a single space in-between) // 1) key format is "source_type destination_type" (a single space in-between)
// 2) the catchall symbol "*" can be used for source_type // 2) the catchall symbol "*" can be used for source_type
@ -240,11 +241,11 @@ jQuery.extend({
}, },
ajaxPrefilter: function( a , b ) { ajaxPrefilter: function( a , b ) {
ajaxPrefilterOrTransport( "prefilters" , a , b ); prefiltersOrTransports( prefilters , a , b );
}, },
ajaxTransport: function( a , b ) { ajaxTransport: function( a , b ) {
return ajaxPrefilterOrTransport( "transports" , a , b ); return prefiltersOrTransports( transports , a , b );
}, },
// Main method // Main method
@ -262,9 +263,6 @@ jQuery.extend({
var // Create the final options object var // Create the final options object
s = jQuery.extend( true , {} , jQuery.ajaxSettings , options ), s = jQuery.extend( true , {} , jQuery.ajaxSettings , options ),
// jQuery lists
jQuery_lastModified = jQuery.lastModified,
jQuery_etag = jQuery.etag,
// Callbacks contexts // Callbacks contexts
// We force the original context if it exists // We force the original context if it exists
// or take it from jQuery.ajaxSettings otherwise // or take it from jQuery.ajaxSettings otherwise
@ -314,23 +312,16 @@ jQuery.extend({
// Builds headers hashtable if needed // Builds headers hashtable if needed
getResponseHeader: function( key ) { getResponseHeader: function( key ) {
var match; var match;
if ( state === 2 ) { if ( state === 2 ) {
if ( !responseHeaders ) { if ( !responseHeaders ) {
responseHeaders = {}; responseHeaders = {};
while( ( match = rheaders.exec( responseHeadersString ) ) ) { while( ( match = rheaders.exec( responseHeadersString ) ) ) {
responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ]; responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ];
} }
} }
match = responseHeaders[ key.toLowerCase() ]; match = responseHeaders[ key.toLowerCase() ];
} }
return match || null; return match || null;
}, },
@ -357,109 +348,27 @@ jQuery.extend({
// State is "done" now // State is "done" now
state = 2; state = 2;
// Dereference transport for early garbage collection
// (no matter how long the jXHR object will be used)
transport = undefined;
// Set readyState
jXHR.readyState = status ? 4 : 0;
// Cache response headers
responseHeadersString = headers || "";
// Clear timeout if it exists // Clear timeout if it exists
if ( timeoutTimer ) { if ( timeoutTimer ) {
clearTimeout(timeoutTimer); clearTimeout(timeoutTimer);
} }
var // Reference dataTypes, converters and responseFields // Dereference transport for early garbage collection
dataTypes = s.dataTypes, // (no matter how long the jXHR object will be used)
converters = s.converters, transport = undefined;
responseFields = s.responseFields,
responseField,
// Flag to mark as success // Cache response headers
isSuccess, responseHeadersString = headers || "";
// Stored success
// Set readyState
jXHR.readyState = status ? 4 : 0;
var isSuccess,
success, success,
// Stored error error = ( statusText = statusText || "error" ),
error, response = responses ? ajaxHandleResponses( s , jXHR , responses ) : undefined,
lastModified,
// To keep track of statusCode based callbacks etag;
oldStatusCode,
// Actual response
response;
// If we got responses:
// - find the right one
// - update dataTypes accordingly
// - set responseXXX accordingly too
if ( responses ) {
var contents = s.contents,
transportDataType = dataTypes[0],
ct,
type,
finalDataType,
firstDataType;
// Auto (xml, json, script or text determined given headers)
if ( transportDataType === "*" ) {
// Remove all auto types
while( dataTypes[0] === "*" ) {
dataTypes.shift();
}
transportDataTypes = dataTypes[0];
// Get content type
ct = jXHR.getResponseHeader( "content-type" );
// Check if it's a known type
for ( type in contents ) {
if ( contents[ type ] && contents[ type ].test( ct ) ) {
dataTypes.unshift( ( transportDataType = type ) );
break;
}
}
}
// Check to see if we have a response for the expected dataType
if ( transportDataType in responses ) {
finalDataType = transportDataType;
} else {
// Try convertible dataTypes
for ( type in responses ) {
if ( ! firstDataType ) {
firstDataType = type;
}
if ( ! transportDataType || converters[ type + " " + transportDataType ] ) {
finalDataType = type;
break;
}
}
// Or just use first one
finalDataType = finalDataType || firstDataType;
}
// If we found a dataType
// We get the corresponding response
// and add the dataType to the list if needed
if ( finalDataType ) {
response = responses[ finalDataType ];
if ( finalDataType !== transportDataType ) {
dataTypes.unshift( finalDataType );
}
}
// Fill responseXXX fields
for( type in responseFields ) {
if ( type in responses ) {
jXHR[ responseFields[ type ] ] = responses[ type ];
}
}
}
// If successful, handle type chaining // If successful, handle type chaining
if ( status >= 200 && status < 300 || status === 304 ) { if ( status >= 200 && status < 300 || status === 304 ) {
@ -467,14 +376,11 @@ jQuery.extend({
// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
if ( s.ifModified ) { if ( s.ifModified ) {
var lastModified = jXHR.getResponseHeader("Last-Modified"), if ( ( lastModified = jXHR.getResponseHeader("Last-Modified") ) ) {
etag = jXHR.getResponseHeader("Etag"); jQuery.lastModified[ s.url ] = lastModified;
if (lastModified) {
jQuery_lastModified[ s.url ] = lastModified;
} }
if (etag) { if ( ( etag = jXHR.getResponseHeader("Etag") ) ) {
jQuery_etag[ s.url ] = etag; jQuery.etag[ s.url ] = etag;
} }
} }
@ -482,114 +388,21 @@ jQuery.extend({
if ( status === 304 ) { if ( status === 304 ) {
statusText = "notmodified"; statusText = "notmodified";
isSuccess = 1; isSuccess = true;
// If we have data // If we have data
} else { } else {
statusText = "success";
// Chain data conversions and determine the final value
// (if an exception is thrown in the process, it'll be notified as an error)
try { try {
success = ajaxConvert( s , response );
var i, statusText = "success";
tmp, isSuccess = true;
// Current dataType
current,
// Previous dataType
prev,
// Conversion expression
conversion,
// Conversion function
conv,
// Conversion functions (when text is used in-between)
conv1,
conv2;
// For each dataType in the chain
for( i = 0 ; i < dataTypes.length ; i++ ) {
current = dataTypes[ i ];
// If a responseXXX field for this dataType exists
// and if it hasn't been set yet
responseField = responseFields[ current ];
if ( responseField && ! ( responseField in jXHR ) ) {
jXHR[ responseField ] = response;
}
// If this is not the first element
if ( i ) {
// Get the dataType to convert from
prev = dataTypes[ i - 1 ];
// If no auto and dataTypes are actually different
if ( prev !== "*" && current !== "*" && prev !== current ) {
// Get the converter
conversion = prev + " " + current;
conv = converters[ conversion ] || converters[ "* " + current ];
// If there is no direct converter, search transitively
if ( ! conv ) {
conv1 = conv2 = undefined;
for( conv1 in converters ) {
tmp = conv1.split( " " );
if ( tmp[ 0 ] === prev || tmp[ 0 ] === "*" ) {
conv2 = converters[ tmp[ 1 ] + " " + current ];
if ( conv2 ) {
conv1 = converters[ conv1 ];
if ( conv1 === true ) {
conv = conv2;
} else if ( conv2 === true ) {
conv = conv1;
}
break;
}
}
}
}
// If we found no converter, dispatch an error
if ( ! ( conv || conv1 && conv2 ) ) {
throw conversion;
}
// If found converter is not an equivalence
if ( conv !== true ) {
// Convert with 1 or 2 converters accordingly
response = conv ? conv( response ) : conv2( conv1( response ) );
}
}
// If it is the first element of the chain
// and we have a dataFilter
} else if ( s.dataFilter ) {
// Apply the dataFilter
response = s.dataFilter( response , current );
// Get dataTypes again in case the filter changed them
dataTypes = s.dataTypes;
}
}
// End of loop
// We have a real success
success = response;
isSuccess = 1;
// If an exception was thrown
} catch(e) { } catch(e) {
// We have a parsererror // We have a parsererror
statusText = "parsererror"; statusText = "parsererror";
error = "" + e; error = "" + e;
} }
} }
// if not success, mark it as an error
} else {
error = statusText = statusText || "error";
} }
// Set data for the fake xhr object // Set data for the fake xhr object
@ -604,9 +417,8 @@ jQuery.extend({
} }
// Status-dependent callbacks // Status-dependent callbacks
oldStatusCode = statusCode; jXHR.statusCode( statusCode );
statusCode = undefined; statusCode = undefined;
jXHR.statusCode( oldStatusCode );
if ( s.global ) { if ( s.global ) {
globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ) , globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ) ,
@ -635,13 +447,13 @@ jQuery.extend({
jXHR.statusCode = function( map ) { jXHR.statusCode = function( map ) {
if ( map ) { if ( map ) {
var tmp; var tmp;
if ( statusCode ) { if ( state < 2 ) {
for( tmp in map ) { for( tmp in map ) {
statusCode[ tmp ] = [ statusCode[ tmp ] , map[ tmp ] ]; statusCode[ tmp ] = [ statusCode[ tmp ] , map[ tmp ] ];
} }
} else { } else {
tmp = map[ jXHR.status ]; tmp = map[ jXHR.status ];
jXHR.done( tmp ).fail( tmp ); jXHR.then( tmp , tmp );
} }
} }
return this; return this;
@ -652,7 +464,7 @@ jQuery.extend({
s.url = ( "" + ( url || s.url ) ).replace( rhash , "" ); s.url = ( "" + ( url || s.url ) ).replace( rhash , "" );
// Extract dataTypes list // Extract dataTypes list
s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( /\s+/ ); s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( rspacesAjax );
// Determine if a cross-domain request is in order // Determine if a cross-domain request is in order
if ( ! s.crossDomain ) { if ( ! s.crossDomain ) {
@ -712,11 +524,11 @@ jQuery.extend({
// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
if ( s.ifModified ) { if ( s.ifModified ) {
if ( jQuery_lastModified[ s.url ] ) { if ( jQuery.lastModified[ s.url ] ) {
requestHeaders[ "if-modified-since" ] = jQuery_lastModified[ s.url ]; requestHeaders[ "if-modified-since" ] = jQuery.lastModified[ s.url ];
} }
if ( jQuery_etag[ s.url ] ) { if ( jQuery.etag[ s.url ] ) {
requestHeaders[ "if-none-match" ] = jQuery_etag[ s.url ]; requestHeaders[ "if-none-match" ] = jQuery.etag[ s.url ];
} }
} }
@ -751,9 +563,7 @@ jQuery.extend({
// If no transport, we auto-abort // If no transport, we auto-abort
if ( ! transport ) { if ( ! transport ) {
done( 0 , "notransport" ); done( 0 , "notransport" );
} else { } else {
// Set state as sending // Set state as sending
@ -771,16 +581,13 @@ jQuery.extend({
}, s.timeout); }, s.timeout);
} }
// Try to send
try { try {
transport.send(requestHeaders, done); transport.send(requestHeaders, done);
} catch (e) { } catch (e) {
// Propagate exception as error if not done // Propagate exception as error if not done
if ( status === 1 ) { if ( status === 1 ) {
done(0, "error", "" + e); done(0, "error", "" + e);
jXHR = false; jXHR = false;
// Simply rethrow otherwise // Simply rethrow otherwise
} else { } else {
jQuery.error(e); jQuery.error(e);
@ -879,97 +686,237 @@ jQuery.extend({
}); });
// Base inspection function for prefilters and transports
function inspectPrefiltersOrTransports( structure , options , originalOptions , dataType , tested ) {
dataType = dataType || options.dataTypes[0];
tested = tested || {};
if ( ! tested[ dataType ] ) {
tested[ dataType ] = true;
var list = structure[ dataType ],
i = 0,
length = list ? list.length : 0,
executeOnly = structure === prefilters,
selected;
for( ; ( executeOnly || ! selected ) && i < length ; i++ ) {
selected = list[ i ]( options , originalOptions );
// If we got redirected to a different dataType,
// we add it and switch to the corresponding list
if ( typeof( selected ) === "string" && selected !== dataType ) {
options.dataTypes.unshift( selected );
selected = inspectPrefiltersOrTransports(
structure , options , originalOptions , selected , tested );
// We always break in order not to continue
// to iterate in previous list
break;
}
}
// If we're only executing or nothing was selected
// we try the catchall dataType
if ( ! tested[ "*" ] && ( executeOnly || ! selected ) ) {
selected = inspectPrefiltersOrTransports(
structure , options , originalOptions , "*" , tested );
}
// This will be ignored by ajaxPrefilter
// so it's safe to return no matter what
return selected;
}
}
function addToPrefiltersOrTransports( structure , dataTypeExpression , functor ) {
var dataTypes = dataTypeExpression.split( rspacesAjax ),
i = 0,
length = dataTypes.length,
dataType,
list,
placeBefore;
// For each dataType in the dataTypeExpression
for( ; i < length ; i++ ) {
dataType = dataTypes[ i ];
// We control if we're asked to add before
// any existing element
placeBefore = /^\+/.test( dataType );
if ( placeBefore ) {
dataType = dataType.substr( 1 );
}
list = structure[ dataType ] = structure[ dataType ] || [];
// then we add to the structure accordingly
list[ placeBefore ? "unshift" : "push" ]( functor );
}
}
// Base function for both ajaxPrefilter and ajaxTransport // Base function for both ajaxPrefilter and ajaxTransport
function ajaxPrefilterOrTransport( arg0 , arg1 , arg2 ) { function prefiltersOrTransports( structure , arg1 , arg2 , type /* internal */ ) {
var type = jQuery.type( arg1 ), type = jQuery.type( arg1 );
structure = jQuery.ajaxSettings[ arg0 ],
i,
length;
// We have an options map so we have to inspect the structure
if ( type === "object" ) { if ( type === "object" ) {
// We have an options map so we have to inspect the structure
var options = arg1, return inspectPrefiltersOrTransports( structure , arg1 , arg2 );
originalOptions = arg2,
// When dealing with prefilters, we execute only
// (no selection so we never stop when a function
// returns a non-falsy, non-string value)
executeOnly = ( arg0 === "prefilters" ),
inspect = function( dataType, tested ) {
if ( ! tested[ dataType ] ) {
tested[ dataType ] = true;
var list = structure[ dataType ],
selected;
for( i = 0, length = list ? list.length : 0 ; ( executeOnly || ! selected ) && i < length ; i++ ) {
selected = list[ i ]( options , originalOptions );
// If we got redirected to a different dataType,
// we add it and switch to the corresponding list
if ( typeof( selected ) === "string" && selected !== dataType ) {
options.dataTypes.unshift( selected );
selected = inspect( selected , tested );
// We always break in order not to continue
// to iterate in previous list
break;
}
}
// If we're only executing or nothing was selected
// we try the catchall dataType
if ( executeOnly || ! selected ) {
selected = inspect( "*" , tested );
}
// This will be ignored by ajaxPrefilter
// so it's safe to return no matter what
return selected;
}
};
// Start inspection with current transport dataType
return inspect( options.dataTypes[ 0 ] , {} );
} else { } else {
// We're requested to add to the structure // We're requested to add to the structure
// Signature is ( dataTypeExpression , function ) // Signature is ( dataTypeExpression , function )
// with dataTypeExpression being optional and // with dataTypeExpression being optional and
// defaulting to catchAll (*) // defaulting to auto ("*")
type = type === "function"; type = ( type === "function" );
if ( type ) { if ( type ) {
arg2 = arg1; arg2 = arg1;
arg1 = undefined; arg1 = undefined;
} }
arg1 = arg1 || "*";
// We control that the second argument is really a function // We control that the second argument is really a function
if ( type || jQuery.isFunction( arg2 ) ) { if ( type || jQuery.isFunction( arg2 ) ) {
addToPrefiltersOrTransports( structure , arg1 || "*" , arg2 );
var dataTypes = arg1.split( /\s+/ ),
functor = arg2,
dataType,
list,
placeBefore;
// For each dataType in the dataTypeExpression
for( i = 0 , length = dataTypes.length ; i < length ; i++ ) {
dataType = dataTypes[ i ];
// We control if we're asked to add before
// any existing element
placeBefore = /^\+/.test( dataType );
if ( placeBefore ) {
dataType = dataType.substr( 1 );
}
list = structure[ dataType ] = structure[ dataType ] || [];
// then we add to the structure accordingly
list[ placeBefore ? "unshift" : "push" ]( functor );
}
} }
} }
} }
// Handles responses to an ajax request:
// - sets all responseXXX fields accordingly
// - finds the right dataType (mediating between content-type and expecting dataType)
// - returns the corresponding response
function ajaxHandleResponses( s , jXHR , responses ) {
var contents = s.contents,
dataTypes = s.dataTypes,
responseFields = s.responseFields,
ct,
type,
finalDataType,
firstDataType;
// Fill responseXXX fields
for( type in responseFields ) {
if ( type in responses ) {
jXHR[ responseFields[ type ] ] = responses[ type ];
}
}
// Remove auto dataType and get content-type in the process
while( dataTypes[0] === "*" ) {
dataTypes.shift();
if ( ct === undefined ) {
ct = jXHR.getResponseHeader( "content-type" );
}
}
// Check if we're dealing with a known content-type
if ( ct ) {
for ( type in contents ) {
if ( contents[ type ] && contents[ type ].test( ct ) ) {
dataTypes.unshift( type );
break;
}
}
}
// Check to see if we have a response for the expected dataType
if ( dataTypes[0] in responses ) {
finalDataType = dataTypes[0];
} else {
// Try convertible dataTypes
for ( type in responses ) {
if ( ! dataTypes[0] || s.converters[ type + " " + dataTypes[0] ] ) {
finalDataType = type;
break;
}
if ( ! firstDataType ) {
firstDataType = type;
}
}
// Or just use first one
finalDataType = finalDataType || firstDataType;
}
// If we found a dataType
// We add the dataType to the list if needed
// and return the corresponding response
if ( finalDataType ) {
if ( finalDataType !== dataTypes[0] ) {
dataTypes.unshift( finalDataType );
}
return responses[ finalDataType ];
}
}
// Chain conversions given the request and the original response
function ajaxConvert( s , response ) {
// Apply the dataFilter if provided
if ( s.dataFilter ) {
response = s.dataFilter( response , s.dataType );
}
var dataTypes = s.dataTypes,
converters = s.converters,
i,
length = dataTypes.length,
tmp,
// Current and previous dataTypes
current = dataTypes[0],
prev,
// Conversion expression
conversion,
// Conversion function
conv,
// Conversion functions (when text is used in-between)
conv1,
conv2;
// For each dataType in the chain
for( i = 1 ; i < length ; i++ ) {
// Get the dataTypes
prev = current;
current = dataTypes[ i ];
// If current is auto dataType, update it to prev
if( current === "*" ) {
current = prev;
// If no auto and dataTypes are actually different
} else if ( prev !== "*" && prev !== current ) {
// Get the converter
conversion = prev + " " + current;
conv = converters[ conversion ] || converters[ "* " + current ];
// If there is no direct converter, search transitively
if ( ! conv ) {
conv2 = undefined;
for( conv1 in converters ) {
tmp = conv1.split( " " );
if ( tmp[ 0 ] === prev || tmp[ 0 ] === "*" ) {
conv2 = converters[ tmp[ 1 ] + " " + current ];
if ( conv2 ) {
conv1 = converters[ conv1 ];
if ( conv1 === true ) {
conv = conv2;
} else if ( conv2 === true ) {
conv = conv1;
}
break;
}
}
}
}
// If we found no converter, dispatch an error
if ( ! ( conv || conv2 ) ) {
jQuery.error( "No conversion from " + conversion.replace( " " , " to " ) );
}
// If found converter is not an equivalence
if ( conv !== true ) {
// Convert with 1 or 2 converters accordingly
response = conv ? conv( response ) : conv2( conv1( response ) );
}
}
}
return response;
}
})( jQuery ); })( jQuery );