Introduction
In the classic article about Deferred usage in jQuery1.5 (for the translation, see here), we will provide the following description:
$. Ajax () returns an object packed with other deferred-related methods. I discussed promise (), but you'll also find then (), success (), error (), and a host of others. you don't have access to the complete deferred object, though; only the promise, callback-binding methods, and the isRejected () and isResolved () methods, which can be used to check the state of the deferred.
But why not return the whole object? If this were the case, it wocould be possible to muck with the works, maybe pragmatically "resolve" the deferred, causing all bound callbacks to fire before the AJAX request had a chance to complete. therefore, to avoid potentially breaking the whole paradigm, only return the dfd. promise ().
This article is very confusing. I have read it several times before I can understand it. The general meaning is:
$. Ajax () returns an object (jqXHR, which is an encapsulation of native XMLHttpRequest). This object contains deferred-related functions, such as promise (), then (), success (), error (), isRejected (), isResolved ().
But you don't find out, there are no resolve (), resolveWith (), reject (), rejectWith () functions, and these functions are used to change the deferred object process. That is to say, $. ajax () returnsRead-only deferred object.
In the following, erichynds uses the reverse tone to propose why the complete deferred object is not returned, but only the read-only deferred object is returned?
If a complete deferred object is returned, the external program can trigger the callback function of the deferred object at will. It is very likely that the callback function (resolve) is triggered before the end of the AJAX request ), this is contrary to the logic of AJAX.
To avoid accidental changes to the internal process of the task, we should only return the read-only version of deferred (dfd. promise ()).
To illustrate the differences between the Deferred objects returned by $. ajax () and $. deferred (), see the following example:
Copy codeThe Code is as follows:
// All method arrays of the deferred object
Var methods = 'done, resolveWith, resolve, isResolved, then, fail, rejectWith, reject, isRejected, promise '. split (','),
Method,
AjaxMethods = [],
OnlyInDeferredMethods = [];
For (method in $. ajax ()){
If ($. inArray (method, methods )! =-1 ){
AjaxMethods. push (method );
}
}
For (method in $. Deferred ()){
If ($. inArray (method, methods )! =-1 & $. inArray (method, ajaxMethods) =-1 ){
OnlyInDeferredMethods. push (method );
}
}
// The Deferred method list that exists in $. deferred () but does not exist in $. ajax () is:
// ["ResolveWith", "resolve", "rejectWith", "reject"]
Console. log (onlyInDeferredMethods );
Negative textbook
If the objects returned by $. ajax () contain resolve () and resolveWith (), what are the possible impacts?
We will also use examples to illustrate the first example of erichynds:
Copy codeThe Code is as follows:
// $. Get, asynchronous AJAX request
Var req = $. get ('./sample.txt'). success (function (response ){
Console. log ('ajax success ');
}). Error (function (){
Console. log ('ajax error ');
});
// Add another AJAX callback function. At this time, AJAX may have ended or may not have ended.
// Because $. ajax has built-in support for deferred, we can write
Req. success (function (response ){
Console. log ('ajax success2 ');
});
Console. log ('end ');
The execution result is:
END-> AJAX success-> AJAX success2
Modify the jQuery1.5 source code and add the resolve () and resolveWith () functions for the returned values of $. ajax:
Copy codeThe Code is as follows:
// Attach deferreds
Deferred. promise (jqXHR );
JqXHR. success = jqXHR. done;
JqXHR. error = jqXHR. fail;
JqXHR. complete = completeDeferred. done;
// The following two lines are manually added.
JqXHR. resolve = deferred. resolve;
JqXHR. resolveWith = deferred. resolveWith;
Then, execute the following code:
Copy codeThe Code is as follows:
// $. Get, asynchronous AJAX request
Var req = $. get ('./sample.txt'). success (function (response ){
Console. log ('ajax success ');
}). Error (function (){
Console. log ('ajax error ');
});
Req. resolve ();
// Add another AJAX callback function. At this time, AJAX may have ended or may not have ended.
// Because $. ajax has built-in support for deferred, we can write
Req. success (function (response ){
Console. log ('ajax success2 ');
});
Console. log ('end ');
The execution result is as follows:
AJAX success-> AJAX success2-> END
That is to say, before the actual AJAX request ends, the success callback function is triggered and an error occurs.
To better understand all of this, we manually pass some forged parameters to the success callback function:
Copy codeThe Code is as follows:
// $. Get, asynchronous AJAX request
Var req = $. get ('./sample.txt'). success (function (response ){
Console. log ('ajax success ('+ response + ')');
});
Req. resolve ('fake data ');
// Add another AJAX callback function. At this time, AJAX may have ended or may not have ended.
// Because $. ajax has built-in support for deferred, we can write
Req. success (function (response ){
Console. log ('ajax success2 ('+ response + ')');
});
Console. log ('end ');
The execution result is as follows:
AJAX success (Fake data)-> AJAX success2 (Fake data)-> END
Code Analysis
Before going deep into jQuery code, let's take a look at the jQuery. promise documentation:
The deferred. promise () method allows an asynchronous function to prevent other code from interfering with the progress or status of its internal request. the Promise exposes only the Deferred methods needed to attach additional handlers or determine the state (then, done, fail, isResolved, and isRejected), but not ones that change the state (resolve, reject, resolveWith, and rejectWith ).
If you are creating a Deferred, keep a reference to the Deferred so that it can be resolved or rejected at some point. return only the Promise object via deferred. promise () so other code can register callbacks or inspect the current state.
In general, deferred. promise () is used to prevent other code from modifying the internal process of an asynchronous task. The Promise object only publicly adds callback functions and detection functions, but does not include functions that modify the status.
If you manually create a deferred object, you need to maintain the reference to this deferred object to modify the status to trigger the callback function. However, your return value should be deferred. promise (), so that external programs can add callback functions or check the status, rather than modifying the status.
So far, everyone should have a clear understanding of promise. Let's take a look at the following two pieces of code, which have the same functions:
Copy codeThe Code is as follows:
Function getData (){
Return $. get ('/foo /');
}
Function showDiv (){
// Correct code. Recommended Practice.
Return $. Deferred (function (dfd ){
$ ('# Foo'). fadeIn (1000, dfd. resolve );
}). Promise ();
}
$. When (getData (), showDiv (). then (function (ajaxResult ){
Console. log ('the animation AND The AJAX request are both done! ');
});
Copy codeThe Code is as follows:
Function getData (){
Return $. get ('/foo /');
}
Function showDiv (){
// Correct code. This is not recommended.
Return $. Deferred (function (dfd ){
$ ('# Foo'). fadeIn (1000, dfd. resolve );
});
}
$. When (getData (), showDiv (). then (function (ajaxResult ){
Console. log ('the animation AND The AJAX request are both done! ');
});
Although the above two sections of code complete the same task, and it seems that the second section of code is more concise, the second section of code is not recommended.
Because the status changes of the job (showDiv) should be kept inside the job, and do not need to be made public to the outside. to make public, you only need to publish a promise read-only deferred object.
Finally, let's take a look at the source code of Deferred:
Copy codeThe Code is as follows:
// Promise method Array
PromiseMethods = "then done fail isResolved isRejected promise". split (""),
JQuery. extend (
// Complete deferred object (with two callback queues)
Deferred: function (func ){
Var deferred = jQuery. _ Deferred (),
FailDeferred = jQuery. _ Deferred (),
Promise;
// Add then, promise, and deferred methods related to errors
JQuery. extend (deferred ,{
Then: function (doneCallbacks, failCallbacks ){
Deferred. done (doneCallbacks). fail (failCallbacks );
Return this;
},
Fail: failDeferred. done,
RejectWith: failDeferred. resolveWith,
Reject: failDeferred. resolve,
IsRejected: failDeferred. isResolved,
// Return the read-only copy of the deferred object
// If obj is passed as a parameter, the promise method will be added to this obj.
Promise: function (obj ){
If (obj = null ){
If (promise ){
Return promise;
}
Promise = obj = {};
}
Var I = promiseMethods. length;
While (I --){
Obj [promiseMethods [I] = deferred [promiseMethods [I];
}
Return obj;
}
});
// Ensure that only one callback function queue is available. That is to say, one task is either successful or failed.
Deferred. done (failDeferred. cancel). fail (deferred. cancel );
// Delete the cancel Function
Delete deferred. cancel;
// Pass the currently created parameter to the given function
If (func ){
Func. call (deferred, deferred );
}
Return deferred;
});
If you think the above Code is difficult to read, it doesn't matter if I write a simple code like this:
Copy codeThe Code is as follows:
Arr = function (){
Var items = [],
Promise,
Arr = {
Add: function (item ){
Items. push (item );
},
Length: function (){
Return items. length;
},
Clear: function (){
Items = [];
},
Promise: function (){
If (promise ){
Return promise;
}
Var obj = promise = {};
Obj. add = arr. add;
Obj. length = arr. length;
Obj. promise = arr. promise;
Return obj;
}
};
Return arr;
}
The code above defines an Arr to generate an array object, including some methods, such as add (), length (), clear (), promise ().
Promise () returns a copy of the current Arr object. It can only add elements to the object, but cannot empty internal arrays.
Copy codeThe Code is as follows:
Var arr = Arr ();
Arr. add (1 );
Arr. add (2 );
// 2
Console. log (arr. length ());
Arr. clear ();
// 0
Console. log (arr. length ());
Var arr = Arr ();
Arr. add (1 );
Arr. add (2 );
// 2
Console. log (arr. length ());
Var promise = arr. promise ();
Promise. add (3 );
Promise. add (4 );
// 4
Console. log (promise. length ());
// Error: TypeError: promise. clear is not a function
Promise. clear ();
Deferred. promise () and deferred. promise (). promise ()
Do you still remember the two codes that completed the same function mentioned above?
Copy codeThe Code is as follows:
Function getData (){
Return $. get ('/foo /');
}
Function showDiv (){
// The promise () is returned here or the deferred object is returned directly, and the code runs correctly.
Return $. Deferred (function (dfd ){
$ ('# Foo'). fadeIn (1000, dfd. resolve );
}). Promise ();
}
$. When (getData (), showDiv (). then (function (ajaxResult ){
Console. log ('the animation AND The AJAX request are both done! ');
});
Have you ever thought about why both methods can run?
If you go deep into jQuery's source code, you will find that $. when (obj1, obj2,...) will obtain obj1.promise () during internal implementation ():
Copy codeThe Code is as follows:
If (object & jQuery. isFunction (object. promise )){
Object. promise (). then (iCallback (lastIndex), deferred. reject );
}
So let's look at the returned result of showDiv above:
If it is a deferred object, $. when () gets promise through the following method:
$. Deferred (). promise ()
If it is a deferred. promise () object, $. when () gets promise through the following method:
$. Deferred (). promise (). promise ()
So do you mean: $. Deferred (). promise () ===$. Deferred (). promise (). promise ()
We still use examples to verify our ideas:
Copy codeThe Code is as follows:
Var deferred = $. Deferred (),
Promise = deferred. promise ();
// True
Promise = promise. promise ();
// True
Promise = promise. promise ();
Of course, this result is inferred. If we look at the source code of Deferred, we can easily see the result:
Copy codeThe Code is as follows:
Promise: function (obj ){
If (obj = null ){
// Here, if promise already exists (. promise () has been called), it will not be created again
If (promise ){
Return promise;
}
Promise = obj = {};
}
Var I = promiseMethods. length;
While (I --){
Obj [promiseMethods [I] = deferred [promiseMethods [I];
}
Return obj;
}
Summary
1. deferred. promise () returns the read-only attribute of the deferred object.
2. It is recommended that the task not return the deferred object, but return the deferred. promise () object. In this way, the internal process of the task cannot be changed.
3. deferred. promise () === deferred. promise (). promise () (we have come to this conclusion from two aspects: Code reasoning and source code analysis)
This article was originally posted on Sansheng stone and first launched in the blog Park. For more information, see the source.