deferred的底层是基于callbacks实现的,建议再熟悉callbacks的内部机制前提下阅读这篇博客,如果需要了解callbacks可以参考:jQuery使用():Callbacks回调函数列表之异步编程(含源码分析)
本质上done、fail、progress实质上分别指向了缓存三种状态的回调对象的add方法,统一由deferred方法引用管理,resolve、reject、notify同理分别指向了三个状态的回调对象的fire方法(内部实现指向fireWith,为了设置this指向deferred或者promise)。下面使用这六个方法来实现一个模拟异步状态回调事件:
1 var df = $.Deferred(); 2 //注册成功的回调函数 3 df.done(function(a){ 4 console.log(‘ho yeah I do it!‘ + a); 5 }); 6 //注册失败的回调函数 7 df.fail(function(a){ 8 console.log(‘sorry I am loser...‘ + a); 9 }); 10 //注册进行时的函数 11 df.progress(function(a){ 12 console.log("waiting???" + a); 13 }); 14 //用定时器模拟一个异步状态回调事件 >60 表示成功 ; < 50表示失败 ; 60><50表示正在进行 15 setInterval(function(){ 16 var score = Math.random() * 100; 17 if(score > 60){ 18 df.resolve("simpleness"); 19 }else if(score < 50){ 20 df.reject("difficult"); 21 }else{ 22 df.notify(‘be surprised to be dumb‘); 23 } 24 },1000);
通过deferred对象同时管理三个回调对象,让代码语义化实现的更优雅,同时也降低了代码的冗余。添加回调函数的形式于callback。add方法一致,可以实现多个同时添加。但是deferred做的远远不止这些,由于deferred对象是同时存在添加和执行两种方法,为了保证调用只能在特定位置触发,deferred还实现了promise对象只用来实现注册方法,promis对象上只有指向回调对象add的done、fail、progress方法,以及一个then用来更便捷的添加回调函数的方法。
所以上面的方法可以修改为:
1 //用定时器模拟一个异步状态回调事件 >60 表示成功 ; < 50表示失败 ; 60><50表示正在进行 2 function createScore(){ 3 var df = $.Deferred(); 4 setInterval(function(){ 5 var score = Math.random() * 100; 6 if(score > 60){ 7 df.resolve("simpleness"); 8 }else if(score < 50){ 9 df.reject("difficult"); 10 }else{ 11 df.notify(‘be surprised to be dumb‘); 12 } 13 },1000); 14 return df.promise(); 15 } 16 var pom = createScore(); 17 //注册成功的回调函数 18 pom.done(function(a){ 19 console.log(‘ho yeah I do it!‘ + a); 20 }); 21 //注册失败的回调函数 22 pom.fail(function(a){ 23 console.log(‘sorry I am loser...‘ + a); 24 }); 25 //注册进行时的函数 26 pom.progress(function(a){ 27 console.log("waiting???" + a); 28 });
上面的代码修改后,就是一个盗版的ajax的事件反馈机制,在jQuery.ajax中源码就是通过deferred的异步队列来实现的。前面还有提到then更便捷的回调注册方法又是什么呢?下面来看通过then方法改造上面的代码:
//上面的代码18~28行可以采用这段代码替换 pom.then(function(a){ console.log(‘ho yeah I do it!‘ + a); },function(a){ console.log(‘sorry I am loser...‘ + a); },function(a){ console.log("waiting???" + a); });
这个模拟示例可以完全采用这两种代码任意一种实现,那这两种代码存在什么区别呢?区别就是then方法可以传入三个参数,分别对应的是done、fail、progress方法的函数注册,但是then不能给同一个状态注册多个回调函数,而done、fail、progress可以像callback.add()那样同时注册多个函数,因为done、fail、progress本身就是指向add()别称。但是then方法还有另一个功能就是能连续注册来替代这种缺陷,并且还可以接收来自上一个方法的返回值作为参数:
1 pom.then(function(a){ 2 console.log(‘ho yeah I do it!‘ + a); 3 return "oK" 4 },function(a){ 5 console.log(‘sorry I am loser...‘ + a); 6 return "no" 7 },function(a){ 8 console.log("waiting???" + a); 9 return "why" 10 }).then(function(param){ 11 console.log(param);//oK 12 },function(param){ 13 console.log(param);//no 14 },function(param){ 15 console.log(param);//why 16 });
但是需要注意的是,返回值不能是新的deferred对象,如果是一个新的异步延迟对象返回,后面继续使用then方法就是作用在新的异步延迟对象上。
这部分源码是基于jQuery使用():Callbacks回调函数列表之异步编程(含源码分析)的模拟回调函数列表对象实现的,没有测试jQuery的Callbacks对象,代码暂时实现了promise()方法,then()方法没有实现,今天有事,有时间再来添加。
1 function clone(origin, target){ 2 for(var ele in origin){ 3 target[ele] = origin[ele]; 4 } 5 return target; 6 } 7 function Deferred(fuc){ 8 //异步延迟对象 9 var deferred = {} 10 var tuples = [ 11 ["resolve","done",Callback("once memory")], 12 ["reject","fail",Callback("once memory")], 13 ["notify","progress",Callback("memory")] 14 ]; 15 var statesum = true; 16 //异步延迟对象的注册对象 17 var promise = { 18 //返回deferred的promise注册对象 19 //源码中有obj的合并采用extend实现,这里写了一个简单的克隆方法 20 promise:function(obj){ 21 return obj != null ? clone(promise,obj) : promise; 22 } 23 } 24 var pending = true; 25 for(var tuple in tuples){ 26 var list = tuples[tuple][2]; 27 //添加deferred的回调函数注册方法 done fail progress 28 promise[tuples[tuple][1]] = list.add; 29 //添加deferred的回调函数触发执行方法 resolve reject ontify 30 deferred[tuples[tuple][0]] = (function(i,obj){ 31 return function(){ 32 if(pending){ 33 // console.log(this.state); 34 deferred[tuples[i][0]+"With"](obj === deferred ? obj : promise,arguments); 35 tuples[i][0] == "resolve" || tuples[i][0] == "reject" ? pending = false : ""; 36 } 37 return obj; 38 } 39 })(tuple,this); 40 deferred[tuples[tuple][0] + "With"] = list.fireWith; 41 } 42 //将promise合并到deferred上 -- 同样采用克隆方法实现 43 promise.promise(deferred); 44 //fcn执行 上下文指向deferred 参数设置为deferred 45 if(fuc){ 46 fuc.call(deferred,deferred); 47 } 48 return deferred; 49 }
jQuery使用():Deferred有状态的回调列表(含源码)
原文:https://www.cnblogs.com/ZheOneAndOnly/p/10549999.html