引言
在那篇经典的关于jQuery1.5中Deferred使用方法介绍的文章中(译文见这里),有下面一段描述:
$.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().
这段话非常令人费解,我也是看了几遍才看明白。大致的意思是:
$.ajax()返回一个对象(jqXHR,这是对原生的XMLHttpRequest的封装),这个对象包含了deferred相关的函数,比如promise(), then(), success(), error(), isRejected(), isResolved()。
但是你发现没,这里面没有resolve(), resolveWith(), reject(), rejectWith() 几个函数,而这几个函数才是用来改变deferred对象流程。也就是说$.ajax()返回了一个只读的deferred对象。
下面erichynds改用反问的语气提出,为什么不返回完整的deferred对象,而只返回只读的deferred对象?
如果返回完整的deferred对象,那么外部程序就能随意的触发deferred对象的回调函数,很有可能在AJAX请求结束前就触发了回调函数(resolve),这就是与AJAX本身的逻辑相违背了。
所以为了避免不经意间改变任务的内部流程,我们应该只返回deferred的只读版本(dfd.promise())。
为了说明$.ajax()和$.Deferred()返回的deferred对象的不同,请看下面的例子:
复制代码 代码如下:
// deferred对象所有的方法数组
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);
}
}
// 存在于$.Deferred(),但是不存在于$.ajax()的deferred相关方法列表为:
// ["resolveWith", "resolve", "rejectWith", "reject"]
console.log(onlyInDeferredMethods);
反面教材
如果$.ajax()返回的对象包含resolve(), resolveWith(),可能会产生哪些影响呢?
我们还是用例子也说明,首先看看erichynds原文的第一个例子:
复制代码 代码如下:
// $.get, 异步的AJAX请求
var req = $.get('./sample.txt').success(function (response) {
console.log('AJAX success');
}).error(function () {
console.log('AJAX error');
});
// 添加另外一个AJAX回调函数,此时AJAX或许已经结束,或许还没有结束
// 由于$.ajax内置了deferred的支持,所以我们可以这样写
req.success(function (response) {
console.log('AJAX success2');
});
console.log('END');
执行结果为:
END -> AJAX success -> AJAX success2
下面修改jQuery1.5源代码,为$.ajax()的返回值添加resolve()和resolveWith()函数:
复制代码 代码如下:
// Attach deferreds
deferred.promise( jqXHR );
jqXHR.success = jqXHR.done;
jqXHR.error = jqXHR.fail;
jqXHR.complete = completeDeferred.done;
// 下面两行是我们手工增加的,jQuery源代码中没有
jqXHR.resolve = deferred.resolve;
jqXHR.resolveWith = deferred.resolveWith;
然后,执行下面代码:
复制代码 代码如下:
// $.get, 异步的AJAX请求
var req = $.get('./sample.txt').success(function (response) {
console.log('AJAX success');
}).error(function () {
console.log('AJAX error');
});
req.resolve();
// 添加另外一个AJAX回调函数,此时AJAX或许已经结束,或许还没有结束
// 由于$.ajax内置了deferred的支持,所以我们可以这样写
req.success(function (response) {
console.log('AJAX success2');
});
console.log('END');
此时的执行结果为:
AJAX success -> AJAX success2 -> END
也就是说,在真实的AJAX请求结束之前,success的回调函数就已经被触发了,出现错误。
为了更清楚的看清这一切,我们手工给success回调函数传递一些伪造的参数:
复制代码 代码如下: