Added deferred to core. Used internally for DOM readyness and ajax callbacks.

This commit is contained in:
jaubourg 2010-12-20 19:09:15 +01:00 committed by jaubourg
parent c1625f6b79
commit 5bacb53866
3 changed files with 458 additions and 187 deletions

View file

@ -59,9 +59,9 @@ var jQuery = function( selector, context ) {
// Has the ready events already been bound?
readyBound = false,
// The functions to execute on DOM ready
readyList = [],
// The deferred used on DOM ready
readyList,
// The ready event handler
DOMContentLoaded,
@ -75,7 +75,10 @@ var jQuery = function( selector, context ) {
indexOf = Array.prototype.indexOf,
// [[Class]] -> type pairs
class2type = {};
class2type = {},
// Marker for deferred
deferredMarker = [];
jQuery.fn = jQuery.prototype = {
init: function( selector, context ) {
@ -252,23 +255,13 @@ jQuery.fn = jQuery.prototype = {
each: function( callback, args ) {
return jQuery.each( this, callback, args );
},
ready: function( fn ) {
ready: function() {
// Attach the listeners
jQuery.bindReady();
// If the DOM is already ready
if ( jQuery.isReady ) {
// Execute the function immediately
fn.call( document, jQuery );
// Otherwise, remember the function for later
} else if ( readyList ) {
// Add the function to the wait list
readyList.push( fn );
}
return this;
// Change ready & apply
return ( jQuery.fn.ready = readyList.then ).apply( this , arguments );
},
eq: function( i ) {
@ -415,23 +408,11 @@ jQuery.extend({
}
// If there are functions bound, to execute
if ( readyList ) {
// Execute all of them
var fn,
i = 0,
ready = readyList;
// Reset the list of functions
readyList = null;
while ( (fn = ready[ i++ ]) ) {
fn.call( document, jQuery );
}
// Trigger any bound ready events
if ( jQuery.fn.trigger ) {
jQuery( document ).trigger( "ready" ).unbind( "ready" );
}
readyList.fire( document , [ jQuery ] );
// Trigger any bound ready events
if ( jQuery.fn.trigger ) {
jQuery( document ).trigger( "ready" ).unbind( "ready" );
}
}
},
@ -800,6 +781,160 @@ jQuery.extend({
now: function() {
return (new Date()).getTime();
},
// Create a simple deferred (one callbacks list)
_deferred: function( cancellable ) {
// cancellable by default
cancellable = cancellable !== false;
var // callbacks list
callbacks = [],
// stored [ context , args ]
fired,
// to avoid firing when already doing so
firing,
// flag to know if the deferred has been cancelled
cancelled,
// the deferred itself
deferred = {
// then( f1, f2, ...)
then: function() {
if ( ! cancelled ) {
var args = arguments,
i,
type,
_fired;
if ( fired ) {
_fired = fired;
fired = 0;
}
for ( i in args ) {
i = args[ i ];
type = jQuery.type( i );
if ( type === "array" ) {
this.then.apply( this , i );
} else if ( type === "function" ) {
callbacks.push( i );
}
}
if ( _fired ) {
deferred.fire( _fired[ 0 ] , _fired[ 1 ] );
}
}
return this;
},
// resolve with given context and args
// (i is used internally)
fire: function( context , args , i ) {
if ( ! cancelled && ! fired && ! firing ) {
firing = 1;
try {
for( i = 0 ; ! cancelled && callbacks[ i ] ; i++ ) {
cancelled = ( callbacks[ i ].apply( context , args ) === false ) && cancellable;
}
} catch( e ) {
cancelled = cancellable;
jQuery.error( e );
} finally {
fired = [ context , args ];
callbacks = cancelled ? [] : callbacks.slice( i + 1 );
firing = 0;
}
}
return this;
},
// resolve with this as context and given arguments
resolve: function() {
deferred.fire( this , arguments );
return this;
},
// cancelling further callbacks
cancel: function() {
if ( cancellable ) {
callbacks = [];
cancelled = 1;
}
return this;
}
};
// Add the deferred marker
deferred.then._ = deferredMarker;
return deferred;
},
// Full fledged deferred (two callbacks list)
// Typical success/error system
deferred: function( func , cancellable ) {
// Handle varargs
if ( arguments.length === 1 ) {
if ( typeof func === "boolean" ) {
cancellable = func;
func = 0;
}
}
var errorDeferred = jQuery._deferred( cancellable ),
deferred = jQuery._deferred( cancellable ),
// Keep reference of the cancel method since we'll redefine it
cancelThen = deferred.cancel;
// Add errorDeferred methods and redefine cancel
jQuery.extend( deferred , {
fail: errorDeferred.then,
fireReject: errorDeferred.fire,
reject: errorDeferred.resolve,
cancel: function() {
cancelThen();
errorDeferred.cancel();
return this;
}
} );
// Make sure only one callback list will be used
deferred.then( errorDeferred.cancel ).fail( cancelThen );
// Call given func if any
if ( func ) {
func.call( deferred , deferred );
}
return deferred;
},
// Check if an object is a deferred
isDeferred: function( object , method ) {
method = method || "then";
return !!( object && object[ method ] && object[ method ]._ === deferredMarker );
},
// Deferred helper
when: function( object , method ) {
method = method || "then";
object = jQuery.isDeferred( object , method ) ?
object :
jQuery.deferred().resolve( object );
object.fail = object.fail || function() { return this; };
object[ method ] = object[ method ] || object.then;
object.then = object.then || object[ method ];
return object;
},
// Use of jQuery.browser is frowned upon.
// More details: http://docs.jquery.com/Utilities/jQuery.browser
@ -818,6 +953,11 @@ jQuery.extend({
browser: {}
});
// Create readyList deferred
// also force $.fn.ready to be recognized as a defer
readyList = jQuery._deferred( false );
jQuery.fn.ready._ = deferredMarker;
// Populate the class2type map
jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) {
class2type[ "[object " + name + "]" ] = name.toLowerCase();

View file

@ -17,20 +17,21 @@ jQuery.xhr = function( _native ) {
if ( _native ) {
return jQuery.ajaxSettings.xhr();
}
function reset(force) {
function reset( force ) {
// We only need to reset if we went through the init phase
// (with the exception of object creation)
if ( force || internal ) {
// Reset callbacks lists
callbacksLists = {
success: createCBList(),
error: createCBList(),
complete: createCBList()
};
deferred = jQuery.deferred();
completeDeferred = jQuery._deferred();
xhr.success = xhr.then = deferred.then;
xhr.error = xhr.fail = deferred.fail;
xhr.complete = completeDeferred.then;
// Reset private variables
requestHeaders = {};
responseHeadersString = responseHeaders = internal = done = timeoutTimer = s = undefined;
@ -154,9 +155,9 @@ jQuery.xhr = function( _native ) {
callbackContext = s.context || s;
globalEventContext = s.context ? jQuery(s.context) : jQuery.event;
for ( i in callbacksLists ) {
callbacksLists[i].bind(s[i]);
for ( i in { success:1, error:1, complete:1 } ) {
xhr[ i ]( s[ i ] );
}
// Watch for a new set of requests
@ -355,10 +356,12 @@ jQuery.xhr = function( _native ) {
// Keep local copies of vars in case callbacks re-use the xhr
var _s = s,
_callbacksLists = callbacksLists,
_deferred = deferred,
_completeDeferred = completeDeferred,
_callbackContext = callbackContext,
_globalEventContext = globalEventContext;
// Set state if the xhr hasn't been re-used
function _setState( value ) {
if ( xhr.readyState && s === _s ) {
@ -374,19 +377,21 @@ jQuery.xhr = function( _native ) {
// We're done
_setState( 4 );
// Success
_callbacksLists.success.fire( isSuccess , _callbackContext , success, statusText, xhr);
if ( isSuccess && _s.global ) {
_globalEventContext.trigger( "ajaxSuccess", [xhr, _s, success] );
// Success/Error
if ( isSuccess ) {
_deferred.fire( _callbackContext , [ success , statusText , xhr ] );
} else {
_deferred.fireReject( _callbackContext , [ xhr , statusText , error ] );
}
// Error
_callbacksLists.error.fire( ! isSuccess , _callbackContext , xhr, statusText, error);
if ( !isSuccess && _s.global ) {
_globalEventContext.trigger( "ajaxError", [xhr, _s, error] );
if ( _s.global ) {
_globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ) , [ xhr , _s , isSuccess ? success : error ] );
}
// Complete
_callbacksLists.complete.fire( 1 , _callbackContext, xhr, statusText);
_completeDeferred.fire( _callbackContext, [ xhr , statusText ] );
if ( _s.global ) {
_globalEventContext.trigger( "ajaxComplete", [xhr, _s] );
// Handle the global AJAX counter
@ -419,7 +424,8 @@ jQuery.xhr = function( _native ) {
// Callback stuff
callbackContext,
globalEventContext,
callbacksLists,
deferred,
completeDeferred,
// Headers (they are sent all at once)
requestHeaders,
// Response headers
@ -596,135 +602,10 @@ jQuery.xhr = function( _native ) {
// Init data (so that we can bind callbacks early
reset(1);
// Install callbacks related methods
jQuery.each(callbacksLists, function(name) {
var list;
xhr[name] = function() {
list = callbacksLists[name];
if ( list ) {
list.bind.apply(list, arguments );
}
return this;
};
});
// Return the xhr emulation
return xhr;
};
// Create a callback list
function createCBList() {
var functors = [],
autoFire = 0,
fireArgs,
list = {
fire: function( flag , context ) {
// Save info for later bindings
fireArgs = arguments;
// Remove autoFire to keep bindings in order
autoFire = 0;
var args = sliceFunc.call( fireArgs , 2 );
// Execute callbacks
while ( flag && functors.length ) {
flag = functors.shift().apply( context , args ) !== false;
}
// Clean if asked to stop
if ( ! flag ) {
clean();
}
// Set autoFire
autoFire = 1;
},
bind: function() {
var args = arguments,
i = 0,
length = args.length,
func;
for ( ; i < length ; i++ ) {
func = args[ i ];
if ( jQuery.isArray(func) ) {
list.bind.apply( list , func );
} else if ( isFunction(func) ) {
// Add if not already in
if ( ! pos( func ) ) {
functors.push( func );
}
}
}
if ( autoFire ) {
list.fire.apply( list , fireArgs );
}
},
unbind: function() {
var i = 0,
args = arguments,
length = args.length,
func,
position;
if ( length ) {
for( ; i < length ; i++ ) {
func = args[i];
if ( jQuery.isArray(func) ) {
list.unbind.apply(list,func);
} else if ( isFunction(func) ) {
position = pos(func);
if ( position ) {
functors.splice(position-1,1);
}
}
}
} else {
functors = [];
}
}
};
// Get the index of the functor in the list (1-based)
function pos( func ) {
for (var i = 0, length = functors.length; i < length && functors[i] !== func; i++) {
}
return i < length ? ( i + 1 ) : 0;
}
// Clean the object
function clean() {
// Empty callbacks list
functors = [];
// Inhibit methods
for (var i in list) {
list[i] = jQuery.noop;
}
}
return list;
}
jQuery.extend(jQuery.xhr, {
// Add new prefilter