5. Asynchronous queue Deferred
5.1 Overview
Asynchronous queue is a chained object that enhances the management and invocation of callback functions and is used to process asynchronous tasks.
Asynchronous queues have three states: initialization (unresolved), success (resolved), and failure (rejected).
Which callback functions are executed depends on the state.
After the status changes to resolved or rejected, it will remain unchanged.
The binding of the callback function can be synchronous or asynchronous, that is, it can be bound at any time.
(The addition of binding registration in this section has the same meaning)
5.2 Key methods
Let’s first look at the key methods in jQuery. Deferred()
Classification
Method
Description
Add
deferred.done()
Add a success callback function
Call immediately when the status is successful (resolved)
deferred.fail()
Add a failure callback function
status Call
deferred.then() immediately when rejected (rejected)
Add success callback function and failure callback function to their respective queues
Convenience method, the two parameters can be arrays or null
status Immediately call the success callback function when the status is successful (resolved)
Immediately call the failure callback function when the status is failed (rejected)
deferred.always()
Add the callback function and add it to the success queue and failure queue at the same time
Call the callback function immediately when the status is determined (regardless of success or failure)
Execute
deferred.resolve()
Call the success callback function queue
Achieve by calling deferred.resolveWith()
deferred.resolveWith()
Execute the success callback function using the specified context and parameters
deferred.reject()
Call the failure callback function queue
Implement
deferred by calling deferred.rejectWith(). rejectWith()
Execute the failure callback function queue using the specified context and parameters
Others
deferred.isRejected()
Determine whether the status is successful (resolved)
deferred.isResolved()
Determine whether the status is failed (rejected)
deferred.pipe()
Call the incoming success filter function or failure filter function before each call to the callback function, and use the return value of the filter function as the callback function The parameter
finally returns a read-only view (implemented by calling promise)
deferred.promise()
returns the read-only view of deferred
Next, the source code of jQuery._Deferred and jQuery.Deferred will be analyzed in detail .
5.3 jQuery._Deferred
Local variables
// References:
// Official website documentation http://api.jquery.com/category/deferred-object/
// Deferred mechanism http:// www.cnblogs.com/fjzhou/archive/2011/05/30/jquery-source-3.html
// Using deferred objects in jQuery 1.5 http://developer.51cto.com/art/201103/248638 .htm
// Look at Promises with a magnifying glass http://www.cnblogs.com/sanshi/archive/2011/03/11/1981789.html
// Promises/A http://wiki.commonjs .org/wiki/Promises/A
var // Promise methods
// Note that there are no following methods: resolveWith resolve rejectWith reject pipe when cancel
// It is not allowed to call resolve reject cancel, etc.
promiseMethods = "done fail isResolved isRejected promise then always pipe".split( " " ),
// Static reference to slice
// Static reference to slice method, borrowing chicken to lay eggs
sliceDeferred = []. slice;
_Deferred:
_Deferred: function() {
var // callbacks list
// Callback function array (not translated as queue here to avoid conceptual confusion)
callbacks = [],
// stored [ context, args ]
// Stores context and parameters, and can also identify whether the execution is completed (fired is non-empty, which means it has been completed)
// "Complete" here It means that all the "existing" functions in the callback function array have been executed;
// But you can call done again to add a callback function, and when added, fired will be reset to 0
fired,
// to avoid firing when already doing so
// If the trigger is already being executed, avoid firing again
firing,
// flag to know if the deferred has been canceled
// Identifies whether the asynchronous queue has been canceled Cancel, the call to done resolve resolveWith will be ignored after cancellation
cancelled,
// Asynchronous queue definition (this is the real owner, the local variables above are referenced through closures)
// the deferred itself
deferred = {
// done( f1, f2, ...)
// Add a success callback function, which will be called immediately when the status is successful (resolved)
done: function() {
// If canceled, ignore this call
if ( !cancelled ) {
// The advantage of defining local variables used in subsequent code at the beginning of the code block:
// 1. Declaration Variables, increase code readability;
// 2. Shared variables, improve performance
// Note: With many years of experience in writing Java, I have developed the habit of defining global variables at the beginning and temporary variables as they are used. It seems that JavaScript is a little different.
var args = arguments, // Callback function array
i, // Traverse variable
length, // Callback function array length
elem, // Single callback function
type, // elem type
_fired; // Used for temporary backup of fired (the context and parameters are stored in fired)
// If the execution is completed (that is, the context and parameters are retained in fired)
// Back up the context and parameters to _fired, and set fired to 0
if ( fired ) {
_fired = fired;
fired = 0;
}
// Add arguments function in callback function array
for ( i = 0, length = args.length; i < length; i ) {
elem = args[ i ];
type = jQuery.type( elem );
// If it is an array, call recursively
if ( type === "array" ) {
// Force the context to be specified as deferred. I personally think there is no need to specify the context here because the default The context is deferred
deferred.done.apply( deferred, elem );
} else if ( type === "function" ) {
callbacks.push( elem );
}
}
// If it has been executed (_fired indicates that the status of the Deferred is determined), execute the newly added function immediately
// Use the previously specified context context and parameter args
if ( _fired ) {
deferred.resolveWith( _fired[ 0 ], _fired[ 1 ] );
}
}
return this;
},
// resolve with given context and args
// Execute, use the specified context and parameters
resolveWith: function( context, args ) {
// It will only be executed if all the following conditions are met: no cancellation, no execution, no execution completed
// If it has been canceled or completed or is being executed, this call is ignored
if ( !cancelled && !fired && !firing ) {
// make sure args are available (#8421)
// Ensure that args is available, a common technique to avoid ReferenceError caused by null and undefined
args = args || [];
// Change firing to 1 during execution
firing = 1;
try {
// Tips for traversing dynamic arrays
while( callbacks[ 0 ] ) {
// Note that the specified context is used here, not this
callbacks.shift().apply( context, args );
}
}
// JavaScript supports try/catch/finally
finally {
fired = [ context, args ];
firing = 0;
}
}
return this;
},
// resolve with this as context and given arguments
// Set the status to Resolved
// The understanding of the setting is not accurate, because whether Resolved is obtained by calling isResolved to determine the status of firing and fired.
// Can be understood as executing
resolve: function() {
deferred.resolveWith( this, arguments );
return this;
},
// Has this deferred been resolved?
// Has it been executed (or resolved)?
// During execution or after execution, it is considered to have been executed/resolved
// "Already" may be inaccurate, because it is also considered to have been executed during execution
isResolved: function() {
// Running
// or
// Finished (i.e. fired is not empty/0)
return !!( firing || fired );
},
// Cancel
// Cancel the asynchronous queue
// Set the flag bit and clear the function queue
cancel: function() {
cancelled = 1;
callbacks = [];
return this;
}
};
return deferred;
}
5.4 jQuery.Deferred
// Full fledged deferred (two callbacks list)
// Create a complete asynchronous queue (including two callback function arrays)
// The asynchronous queue has three states: initialization (unresolved) , success (resolved), failure (rejected).
// Which callback functions are executed depends on the state.
// After the status changes to resolved or rejected, it will remain unchanged.
Deferred: function( func ) {
// _Deferred has no success or failure status. It has four states: initialization, execution, execution completed, canceled
// For code reuse, internal First implemented a _Deferred
// failDeferred referenced through closure
var deferred = jQuery._Deferred(),
failDeferred = jQuery._Deferred(),
promise;
// Add errorDeferred methods, then and promise
jQuery.extend( deferred, {
// Add success callback function and failure callback function to their respective queues
// Convenience method, the two parameters can be arrays or null
// Immediately call the success callback function when the status is successful (resolved)
// Immediately call the failure callback function when the status is failed (rejected)
then: function( doneCallbacks, failCallbacks ) {
/ / There is a context switch here: although done returns a deferred, fail points to failDeferred.done. When fail is executed, the context changes to failDeferred
// To put it simply:
// Add a callback to the deferred when calling done Function doneCallbacks
// Add a callback function to failDeferred when calling fail failCallbacks
// Therefore, after this line of expression is executed, failDeferred
deferred.done( doneCallbacks ).fail( failCallbacks );
// Force return deferred
return this;
},
// Register a callback function, which will be called whether it is resolved or rejected.
// In fact, the function (array) passed in is added to deferred and failDeferred at the same time
// It is not stored in a separate function array as I imagined
always: function( ) {
// The context of done is set to deferred, and the context of fail is set to this
// Are the contexts of done and fail inconsistent? Consistent! Here this is equal to deferred
// But how should we interpret the context set like this? How is it different from the implementation of then?
// fail points to fail and fails to failDeferred.done. The default context is failDeferred. The callback function array callbacks of failDeferred are referenced through closures.
// Although the context of the failDeferred.done method is set to deferred, It does not affect the execution of failDeferred.done.
// Replace this with deferred at the end of failDeferred.done to implement chain calls.
// That is, the context this is not lost during the call process and you can continue to call other chain calls. method without causing this confusion
// Grammatically, the effect to be achieved by always is consistent with the effect to be achieved by then
// Therefore, this line of code can be rewritten into two lines (similar to the implementation of then ), the effect is equivalent:
// deferred.done( arguments ).fail( arguments );
// returnr this;
return deferred.done.apply( deferred, arguments ).fail. apply( this, arguments );
},
// Add failure callback function
// Called immediately when the status is failed (rejected)
fail: failDeferred.done,
// Use The specified context and parameters execute the failure callback function queue
// By calling failDeferred.rejectWith()
rejectWith: failDeferred.resolveWith,
// The failure callback function queue is called
// By calling failDeferred .resolve() implements
reject: failDeferred.resolve,
// Determine whether the status is successful (resolved)
isRejected: failDeferred.isResolved,
// Call the passed callback function each time Enter the success filter function or failure filter function, and use the return value of the filter function as the parameter of the callback function
// Finally return a read-only view (call promise to implement)
// Whether fnDone is in the status of success ( is called when resolved)
// fnFail is called when the status is rejected (rejected)
// About other explanations:
// 1. Some articles are translated as "pipeline mechanism", from The literal meaning cannot be understood, so at least it is inaccurate
// 2. Wrong understanding: The so-called pipe just puts the incoming fnDone and fnFail at the head of the array of the success queue and the failure queue
pipe : function( fnDone, fnFail ) {
return jQuery.Deferred(function( newDefer ) {
jQuery.each( {
done: [ fnDone, "resolve" ], // done will point to it later in the text deferred.done
fail: [ fnFail, "reject" ]
}, function( handler, data ) {
var fn = data[ 0 ],
action = data[ 1 ],
returned;
if ( jQuery.isFunction( fn ) ) {
deferred[ handler ](function() {
returned = fn.apply( this, arguments );
if ( returned && jQuery .isFunction( returned.promise ) ) {
returned.promise().then( newDefer.resolve, newDefer.reject );
} else {
newDefer[ action ]( returned );
}
});
} else {
deferred[ handler ]( newDefer[ action ] );
}
});
}).promise();
} ,
// Get a promise for this deferred
// If obj is provided, the promise aspect is added to the object
// What is returned is an incomplete Deferred interface, without resolve and reject , that is, the state of the Deferred object cannot be modified,
// This is to prevent external functions from triggering the callback function early, and can be regarded as a read-only view.
//
// For example, $.ajax no longer returns XMLHttpRequest after version 1.5, but returns an object that encapsulates XMLHttpRequest and Deferred object interfaces.
// The Deferred part is obtained by promise(), which prevents external functions from calling resolve and reject, and prevents the callback function from being triggered before ajax is completed.
// Reserve the calling permission of these two functions to ajax internally.
promise: function( obj ) {
if ( obj == null ) {
// Promise will actually be executed only once, and the result of the first execution is stored in the promise variable
if ( promise ) {
return promise;
}
promise = obj = {};
}
var i = promiseMethods.length;
// Another way to loop through
// I am used to using:
// for( i = 0; i < len; i ) or for( i = len-1; i >=0; i-- ) or for( i = len ; i--; )
// jQuery is really a treasure everywhere!
while( i-- ) {
obj[ promiseMethods[i] ] = deferred[ promiseMethods[i] ];
}
return obj;
}
});
// Make sure only one callback list will be used
// After the successful queue execution is completed, the failure queue cancellation method will be executed
// After the failure queue execution is completed, the successful queue cancellation method will be executed
// Ensure that only one function queue will be executed, that is, either the execution success queue or the execution failure queue;
// That is, the status can only be either success or failure, no cross-calling
// deferred and failDeferred's canceled attribute can only be referenced through closures, so there is no need to worry about confusion of state and context
deferred.done( failDeferred.cancel ).fail( deferred.cancel );
// Unexpose cancel
// Hide the cancel interface, that is, the successful function queue cannot be canceled from the outside
delete deferred.cancel;
// Call given func if any
// Execute the incoming func function
if ( func ) {
func.call( deferred, deferred );
}
return deferred;
}
5.5 jQuery.when
// Deferred helper
// Asynchronous queue tool function
// firstParam: one or Multiple Deferred objects or JavaScript ordinary objects
when: function( firstParam ) {
var args = arguments,
i = 0,
length = args.length,
count = length,
// If arguments.length is equal to 1, and firstParam is Deferred, then deferred=firstParam
// Otherwise, create a new Deferred object (if arguments.length is equal to 0 or greater than 1, create a new Deferred object )
// Simply determine whether it is a Deferred object through jQuery.isFunction( firstParam.promise )
deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ?
firstParam :
jQuery.Deferred();
// Construct a successful (resolve) callback function
function resolveFunc( i ) {
return function( value ) {
// If the parameter passed in is greater than one , then convert the incoming parameters into a real array (arguments does not have a slice method, so borrow a chicken to lay an egg)
args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;
if ( !( --count ) ) {
// Strange bug in FF4:
// Values changed onto the arguments object sometimes end up as undefined values
// outside the $.when method . Cloning the object into a fresh array solves the issue
// Successfully executed callback function queue, the context is forced to the first Deferred object passed in
deferred.resolveWith( deferred, sliceDeferred.call( args, 0 ) );
}
};
}
// If there are more than one parameters
if ( length > 1 ) {
for( ; i < length; i ) {
// Simply determine whether it is a Deferred object, if so, call .promise().then(), otherwise ignore
if ( args[ i ] && jQuery.isFunction( args[ i ].promise ) ) {
//Add success callback function and failure callback function to their respective queues
args[ i ].promise().then( resolveFunc(i), deferred.reject );
} else {
// Counter, indicating that it is found that it is not a Deferred object, but an ordinary JavaScript object
--count;
}
}
// When the counter is 0, it means that none of the parameters passed in are Deferred objects
// Execution success callback function queue, the context is forced to be the first Deferred object passed in
if ( !count ) {
deferred.resolveWith( deferred, args );
}
// deferred !== firstParam, that is, deferred is a newly created Deferred object
// that is, length == 0
} else if ( deferred !== firstParam ) {
// callback function queue for successful execution , the context is forced to the newly created Deferred object
deferred.resolveWith( deferred, length ? [ firstParam ] : [] );
}
// Returns the first Deferred passed in or the newly created Deferred Read-only view of the object
return deferred.promise();
}
5.6 Deferred application
l jQuery.ajax()
n TODO
5.7 Skills that can be learned
l Closure
function a(){
var guid = 1;
return function(){
return guid ;
}
}
var defer = a();
console. info( defer() ); // 1
console.info( defer() ); // 2
console.info( defer() ); // 3
console.info( defer() ); // 4
l Common techniques to avoid ReferenceError caused by null and undefined
args = args || [];
l Techniques to traverse dynamic arrays
while( callbacks[ 0 ] ) {
callbacks.shift ().apply( context, args );
}
l try/catch/finally to implement error handling
Syntax
Description
try {
// tryStatements
} catch ( exception ) {
// catchStatements
} finally {
// finallyStatements
}
tryStatements
Required.
Possible incorrect statements.
exception
Required. any variable name.
The initialization value of exception is the value of the error thrown.
catchStatements
Optional.
Statement that handles errors that occur in the associated tryStatement.
finallyStatements
Optional.
A statement that is executed unconditionally after all other processes have occurred.
l Chained object: Implement chained calls by returning this
Method
Return value
done
this (i.e. deferred)
resolveWith
this (i.e. deferred)
resolve
this (i.e. deferred)
cancel
this (i.e. deferred)
l Code reuse $.each
jQuery.each( {
done: [ fnDone, "resolve " ], // done will point to deferred.done later in the article
fail: [ fnFail, "reject" ]
}, function( handler, data ) {
// Public code reuse
});
5.8 Follow-up
l Application of Deferred in jQuery
l Custom application of Deferred