参考文章:
jQuery的deferred对象详解以及我写过的Promise文章
jQuery.defered最新的文档:http://www.css88.com/jqapi-1.9/category/deferred-object/
前言
jQuery跟人的第一印象就是操作DOM的库,新手学习jQuery也是先从操作DOM学起,我当年也是如此。学会了操作DOM之后再学学事件和Ajax,就以为学成了,然而jQuery.defered对象由于用处不大,我始终没有系统的学习,只是在jQuery.ajax里面间接使用过。到今天,虽然ES6和Vue等新技术很有市场,但是jQuery依然是小型项目的最佳解决方案,更何况我们开发PC页面依然必须支持IE8,我的项目也越写越复杂,所以我认为jQuery.defered对象是IE8中解决回调问题的优选之一。另一套技术方案就是用Promise的polyfill,它的优点是跟原生Promise的语法完全一致,缺点是要专门引入库。
本文只对比两种技术方案的区别,适合于已经熟悉原生Promise理念,也研究过jQuery.defered对象的人阅读。
链式操作举例
找出我从前的一个例子(原文),隔一段时间打印一些字符。原生写法如下:
var promise = new Promise(function(resolve, reject) {
setTimeout(function() {
console.log('第一个回调');
resolve(3);
}, 3000);
});
promise.then(function(value) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
console.log('第二个回调');
console.log(value * 2);
resolve(value * 2);
}, 2000);
});
}).then(function(value) {
setTimeout(function() {
console.log('第三个回调');
console.log(value * 2);
}, 1000);
});
也就是先new一个Promise对象,然后在这个Promise对象身上加then方法。
jQuery.defered()方案写法如下:
var dfd = function() {
var deferred = $.Deferred();
setTimeout(function() {
console.log("执行完毕1");
deferred.resolve(1);
},4000);
return deferred;
};
$.when(dfd())
.then(function(value){
var deferred = $.Deferred();
setTimeout(function() {
console.log("执行完毕2");
console.log('value = ' + value);
deferred.resolve(value + 1);
},3000);
return deferred;
})
.then(function(value){
var deferred = $.Deferred();
setTimeout(function() {
console.log("执行完毕3");
console.log('value = ' + value);
deferred.resolve(value + 1);
},2000);
return deferred;
});
首先研究一下$.when()(文档)
这个方法的正确使用姿势是:传入0个或者多个延迟对象。当全部成功,则执行.done,如果有一个失败,就执行.fail。
在多个延迟对象传递给jQuery.when() 的情况下,.when()根据一个新的“宿主” Deferred(延迟)对象,跟踪所有已通过Deferreds聚集状态,返回一个Promise对象。
当所有的延迟对象被解决(resolve)时,“宿主” Deferred(延迟)对象才会解决(resolved)该方法,或者当其中有一个延迟对象被拒绝(rejected)时,“宿主” Deferred(延迟)对象就会reject(拒绝)该方法。
如果“宿主” Deferred(延迟)对象是解决(resolved)状态时, “宿主” Deferred(延迟)对象的 doneCallbacks (解决回调)将被执行。参数传递给 doneCallbacks提供这解决(resolved)值给每个对应的Deferreds对象,并匹配Deferreds传递给 jQuery.when()的顺序。
再看看构造延迟对象的区别
从书写上,两个例子略有区别,jq的写法是以普通函数return延迟对象,原生写法是用构造函数直接构造延迟对象。那么jq有没有直接构造延迟对象的办法?有。
下面就是以构造函数$.Deferred()
直接构造延迟对象的例子,构造函数会给回调函数的参数传入一个空延迟对象,所以内部不需要var deferred = $.Deferred();
。then里的代码完全相同。
$.Deferred(function(dfd){
setTimeout(function(){
console.log("1执行完毕!");
dfd.resolve(1);
},4000);
return dfd;
})
.then(function(value){
var deferred = $.Deferred();
setTimeout(function(){
console.log("2执行完毕!");
deferred.resolve(value + 1);
},3000);
return deferred;
})
.then(function(value){
var deferred = $.Deferred();
setTimeout(function(){
console.log("3执行完毕!" + value);
deferred.resolve();
},2000);
return deferred;
});
这两种的代码量、理解难易度都差不多,如果你的“第一步操作”只需要创建一个延迟对象,那么用谁都可以,如果你的“第一步操作”需要创建多个延迟对象,而且这些延迟操作是并发关系,那么只能用$.when()
,因为它可以接受多个延迟对象,且会更待所有的操作完成再做出响应。
jQuery.defered对象和原生Promise对象对应关系总结
以下左为jQuery.defered对象,右为原生Promise对象
$.Deferred()类似于new Promise()
deferred.then没有对应任何原生Promise方法,因为deferred.then可以有三个处理程序,第一个是成功后的,第二个是失败后的,第三个是[可选]当Deferred(延迟)对象生成进度通知时被调用的一个函数。也就是说,deferred.then = deferred.done + deferred.fail + progressFilter。
deferred.then是jQuery中唯一可以传递延迟状态的方法。其实在1.8版本之前,没有任何方法可以传递延迟状态,只不过1.8版本开始,jQ的开发者们给then赋予了传递延迟状态的能力,这时候,很多使用者误以为done和fail等也可以传递延迟状态,其实并不是,记住,直至本文完稿为止,只有then方法有这个能力。
再具体说,就是then方法后面可以接done、fail等方法,then可以把延迟状态传递给done和fail等方法,但是,done、fail方法后面不可以接then方法,说白了done、fail就是延迟链的终点,不会再有延迟对象传递。这跟原生Promises是不一样的。
deferred.done也没有对应任何原生Promise方法,千万不要以为deferred.done对应单参数的new Promise().then,因为deferred.done连缀书写的话,各个回调函数是并列关系,回调函数里面如果有异步任务,会导致执行顺序不符合你的期待。
deferred.fail跟deferred.done同理,不要以为deferred.fail对应new Promise().catch。而且,new Promise().catch后面可以继续传递延迟状态,但是deferred.fail不可以。
看了上文,你就也可以知道,deferred.catch也不对应new Promise().catch。deferred.catch是deferred.then( null, fn )的别名,deferred.catch跟deferred.fail的区别在于deferred.fail更强大,可以接受多个函数。
deferred.always也没有对应方法,因为always可以传入处理程序列表,但是这些处理程序是同类并列关系,并不是说前一个函数对应成功,后一个函数对应失败。所以jQuery官方也建议,不要去用if判断上一步到底是成功还是失败,而是应当执行一些无状态的语句,这样才符合always单词的语义。如果想判断,依然推荐你用done和fail。
$.when()对应Promise.all()
没有方法对应Promise.race()
剩余其他deferred方法用得少,暂不举例
现在就可以看出来各方的优势了,原生方法的优势是符合业界标准,理解简单,jQ的优势是除了没有Promise.race(),其他方法很多。所以,技术选型时:
-
如果你确定不需要Promise.race()功能,但很需要jQ自带的一些方法全部方法看这里,那么,选jQuery.defered。
-
如果你很需要Promise.race()功能,并不需要jQ自带的一系列方法,那么用Promise的polyfill,或原生Promise即可。
网友评论