Introduction
In the classic
article about the use of Deferred in jQuery 1.5 (see the translation
here), there is 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 would 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 passage is very confusing, and I read it several times before I understood it. The general meaning is:
$.ajax() returns an object (jqXHR, which is an encapsulation of the native XMLHttpRequest). This object contains deferred-related functions, such as promise(), then(), success() , error(), isRejected(), isResolved().
But have you noticed that there are no resolve(), resolveWith(), reject(), rejectWith() functions in it, and these functions are used to change the deferred object process. In other words, $.ajax() returns a
read-only deferred object .
The following erichynds asked in a rhetorical tone, why not return the complete deferred object, but only the read-only deferred object?
If a complete deferred object is returned, then the external program can trigger the callback function of the deferred object at will. It is very likely that the callback function (resolve) will be triggered before the AJAX request ends. This is contrary to the logic of AJAX itself. .
So in order to avoid inadvertently changing the internal flow of the task, we should only return the read-only version of the deferred (dfd.promise()).
To illustrate the difference in deferred objects returned by $.ajax() and $.Deferred(), please see the following example:
// Array of all methods 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 list of deferred related methods that exist in $.Deferred() but not in $.ajax() is:
// ["resolveWith", "resolve" , "rejectWith", "reject"]
console.log(onlyInDeferredMethods);
Negative teaching material
If the object returned by $.ajax() contains resolve(), resolveWith() , what impacts might it have?
Let’s use examples to illustrate. First, look at the first example of erichynds’ original text:
// $.get, asynchronous AJAX request
var req = $.get('./sample.txt').success(function (response) {
console. log('AJAX success');
}).error(function () {
console.log('AJAX error');
});
// Add another one AJAX callback function, AJAX may have ended at this time, or it may not have ended yet
// Since $.ajax has built-in deferred support, we can write like this
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 below and add resolve() and resolveWith() functions for the return value of $.ajax():
// Attach deferreds
deferred.promise( jqXHR );
jqXHR.success = jqXHR.done;
jqXHR.error = jqXHR.fail;
jqXHR.complete = completeDeferred.done;
// The following two lines were added manually. There is no
jqXHR in the jQuery source code. resolve = deferred.resolve;
jqXHR.resolveWith = deferred.resolveWith;
Then, execute the following code:
// $ .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 one AJAX callback function, AJAX may have ended at this time, or it may not have ended yet
// Since $.ajax has built-in deferred support, we can write like this
req.success(function (response) {
console.log('AJAX success2');
});
console.log('END');
The execution result at this time is:
AJAX success -> AJAX success2 -> END
In other words, before the actual AJAX request ends, the success callback function has been triggered and an error occurs.
In order to see all this more clearly, we manually pass some fake parameters to the success callback function:
// $.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, AJAX may have ended at this time, or it may not have ended yet
// Since $.ajax has built-in deferred support, we can write like this
req.success(function (response) {
console.log('AJAX success2(' response ')');
});
console.log('END');
At this time The execution result is:
AJAX success(Fake data) -> AJAX success2(Fake data) -> END
Code analysis
Before diving into the jQuery code, let’s take a look at jQuery.promise Documentation for:
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.
Roughly speaking, deferred.promise() is used to prevent Other code modifies the internal flow of the asynchronous task. The Promise object only publicly adds callback functions and functions for detecting the state, but does not include functions for modifying the state.
If you create a deferred object manually, then you need to maintain a reference to the deferred object to modify the state to trigger the callback function. However, your return value should be deferred.promise(), so that external programs can add callback functions or detect the state, but cannot modify the state.
At this point, everyone should have a clear understanding of promises. Let’s look at the following two pieces of code. They perform exactly the same functions:
function getData() {
return $.get('/foo/');
}
function showDiv() {
// Correct code. Recommended practices.
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!');
});
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 pieces of code accomplish the same task, and the second piece of code seems to be more concise, the second piece of code is not a recommended approach.
Because changes in the status of the task (showDiv) itself should be kept within the task and do not need to be exposed to the outside world. To the outside world, you only need to expose a read-only deferred object of promise.
Finally, let’s take a look at the Deferred related source code:
//Array of Promise related methods
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 error-related deferred methods
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 A read-only copy of the deferred object
// If obj is passed as a parameter, the promise related 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;
}
});
// Make sure there is only one The callback function queue is available, which means that a task either succeeds or fails
deferred.done(failDeferred.cancel).fail(deferred.cancel);
// Delete cancel function
delete deferred.cancel;
// Pass the currently created one as a parameter to the given function
if (func) {
func.call(deferred, deferred);
}
return deferred;
});
If you find the above code difficult to read, it doesn’t matter. I wrote a simple similar code:
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 above code defines an Arr, which is used to generate an array object, including some methods, such as add(), length(), clear( ), promise().
Where promise() returns a copy of the current Arr object, elements can only be added to it, but the internal array cannot be cleared.
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()
Remember the two codes mentioned earlier that accomplish the same function?
function getData() {
return $.get ('/foo/');
}
function showDiv() {
// Return promise() here or directly return the deferred object, and the code will run 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!');
});
So have you ever thought about why these two methods work?
If you dig into jQuery's source code, you'll find that $.when(obj1, obj2, ...) is internally implemented to get obj1.promise():
if ( object && jQuery.isFunction( object.promise ) ) {
object.promise().then( iCallback(lastIndex), deferred.reject );
}
So let’s look at the return result of showDiv above:
If it is a deferred object, $.when() gets the promise in the following way:
$.Deferred().promise()
If it is a deferred.promise() object, $.when() gets the promise in the following way:
$.Deferred().promise().promise()
Does that mean: $.Deferred().promise() === $.Deferred().promise().promise()
Let’s verify our ideas through examples:
var deferred = $.Deferred(),
promise = deferred.promise();
/ / true
promise === promise.promise();
// true
promise === promise.promise().promise().promise();
Of course, this result is inferred. If we look directly at the source code of Deferred, it is easy to see this result:
promise: function (obj) {
if (obj == null) {
// Here, if promise already exists (has been called .promise()), it will not be recreated
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 property of the deferred object.
2. It is recommended that tasks do not return deferred objects, but return deferred.promise() objects. In this way, external parties cannot arbitrarily change the internal process of the task.
3. deferred.promise() === deferred.promise().promise() (We reached this conclusion from two perspectives: code reasoning and source code analysis)
This article is written by Sanshengshi Original on the blog, first published on Blog Park, please indicate the source when reprinting.