Promise对象和Deferred对象

一、从Ajax请求说起

Promise和Deferred对象为我们提供了一种很优雅的异步处理方案,Promise最开始出现于Dojo框架中,09年Kris Zyp有感于dojo.Deferred提出了CommonJS之Promise/A规范,jQuery从1.5版本开始实现了这一重量级方案,不过没有严格按照规范进行实现,有一些API上的差异。说到这里,先来看下这两个对象能够做些什么吧!

从一个ajax请求说起,如果有一个异步的ajax请求,按照一般的实现方式,包括我现在也还在这样做:

JavaScript代码
  1.     
  2. $.get('/myRequest',function(){      
  3.    success: onSuccess,      
  4.    failure: onFailure,      
  5.    always : onAlways      
  6. })  

在这种常规的实现方案中,会把异步的回调参数和请求参数写在一起做为ajax的参数,但是如果通过promise的实现方式,这种写法可以转变为这样:

JavaScript代码
  1.     
  2. var promise = $.get("/myRequest");      
  3. promise.done(onSuccess);      
  4. promise.fail(onFailure);      
  5. promise.always(onAlways);  

这种方式和普通的调用方式有什么不同的地方呢?

在这种方式中可以和EventEmitter一样可以任意添加done、fail、always的处理方法,最主要的作用还是把请求与回调进行了分离与解耦,通过这样的封装还可以做更多好玩的事,接着往下看吧。

promise对象中的方法只能被动的被执行,如果需要主动触发这些事件怎么办呢,这时我们可以使用Deferred对象,jQuery中可以这样使用(摘自《javascript异步编程》):

JavaScript代码
  1.     
  2. var prompt = new $.Deferered();      
  3. prompt.always(function(){      
  4.    console.info("A choice was made:);    
  5. });     
  6. prompt.done(function(){     
  7.    console.info("Starting game...");    
  8. });     
  9. prompt.fail(function(){     
  10.    console.info("No game tody.");    
  11. });     
  12. $("#playGame").focus.on("keypress",function(e){    
  13.    if(e.keyCode === "121"){      
  14.       prompt.resolve();      
  15.    }else if(e.keyCode == 110){      
  16.       prompt.reject();      
  17.    }else{      
  18.       return false;      
  19.    }      
  20. })  

这样就可以通过键盘发出不同的指令了,Promise与Deferred对象不同的地方便是Deferred可以通过resolve和reject方法触发对应的回调,Deferred是Promise对象的一个超集,可以通过Deferred对象的promise方法获取对应的Promise对象,说到这里,有一个很关键的名词 -state(状态),在对象内部会维持这样一个状态值的变化,这样的状态控制机制使得done、fail 方法只会被响应一次:

二、jQuery中使用promise和deferred对象

promise和deferred对象被发扬光大一定程度上是由于在jQuery中得到支持,这里简单介绍一些jQuery中的主要用法:

1、promise对象的链式调用

JavaScript代码
  1.     
  2. $.ajax("test.cgi").      
  3.   done(function(){console.info("called first time");}).      
  4.   fail(function(){console.info("error");}).      
  5.   done(function(){console.info("called second time")});  

2、处理动画回调

JavaScript代码
  1.     
  2. var $flash = $("#flash");      
  3. var showpromise = $flash.fadeIn().promise();      
  4. var hidePromise = $flash.fadeOut().promise();      
  5. showpromise.done(function(){console.info("fadeIn call back")});      
  6. hidePromise.done(function(){console.info("fadeOut call back")})  

通过promise对象把动画与回调进行了很好的解耦,无需再把callback以参数的形式进行传递,这只是jQuery在1.6以及1.7中的权宜之计,使用Deferred对象也可以达到同样的效果

JavaScript代码
  1.     
  2. var $flash = $("#flash");      
  3. var defer = new $.Deferred();      
  4. $flash.fadeOut(defer.resolve);      
  5. defer.done(function(){console.info("fadeIn call back")});  

3、如何给回调传递参数

JavaScript代码
  1.     
  2. var def = new $.Deferred();      
  3. def.done(function(name){      
  4.    console.info("done with name %s",name);      
  5. });      
  6. def.resolve("test");      
  7. //也可以传入上下文对象      
  8. def.resolveWith(context,"test");      
  9. //也可以这样调用      
  10. def.resolve.call(context,"test");  

4、进度通知

Promise主要提供对结果的监控,在jQuery中也可以通过progress事件对过程进行处理

JavaScript代码
  1.     
  2. var def = new $.Deferred();      
  3. def.progress(function(count){      
  4.    console.info(count);      
  5. });      
  6. def.notify(1);      
  7. def.notify(2);      
  8. ...  

5、对多个异步事件指定回调

JavaScript代码
  1.     
  2. $.when($.post("/1",data1),$.post("/2",data2)).      
  3.   then(onSuccess,onFailure);  

当两个post请求结束时才会执行then方法,then方法的第一个参数做为done的回调,第二个参数作为failure的回调,如果只有一个参数则作为done的回调方法。那如何处理每一个Promise对象的参数呢?每一个Promise对象返回的结果会按照顺序传给done方法,所以我们可以这样使用:

JavaScript代码
  1.     
  2. $.when($.post("/1",data1),$.post("/2",data2)).      
  3.   done(function(post1_Args,post2_Args){      
  4.      //...      
  5.   });  

但是这样写法不是很被推荐,可读性较好的写法可以这样处理多个promise对象的结果:

JavaScript代码
  1.     
  2. var data = {};      
  3. var getting1 = $.get("/1").      
  4.     done(function(result){data["1"] = result;});      
  5. var getting2 = $.get("/2").      
  6.     done(function(result){data["2"]= result;});      
  7. $.when(getting1,getting2).done(function(){      
  8.     //此处访问data已经拿到两个promise对象返回的数据      
  9. });  

如果$.when传入的参数不是Promise或者Deferred对象,则会视为一个已经resolved的Deferred对象并立即执行,如下所示:

JavaScript代码
  1.     
  2. $.when({test:"123"}).done(function(data){      
  3.    console.info(data.test); // ==> 123      
  4. })      
  5. //或者这样写      
  6. $.when(test()).done(function(data){      
  7.    console.info(data.test); // ==> 123      
  8. })      
  9. function test(){      
  10.    return {test: "123"}      
  11. }  

基于when方法这样的特性,如果when方法的参数是function并且返回的Deferred对象,则可以做这样一些异步的处理:

JavaScript代码
  1.     
  2. var wait1 = function(){      
  3.    var def = $.Deferred();      
  4.    var tasks = function(){        
  5.       def.resolve("方法1执行成功");      
  6.    };      
  7.    setTimeout(tasks,1000);      
  8.    return def;      
  9. }      
  10. var wait2 = function(){      
  11.    var def = $.Deferred();      
  12.    var tasks = function(){        
  13.       def.resolve("方法2执行成功");      
  14.    };      
  15.    setTimeout(tasks,2000);      
  16.    return def;      
  17. }      
  18. $.when(wait1(),wait2()).done(function(data1,data2){      
  19.    alert(data1 + "," + data2) // ==> 方法1执行成功,方法2执行成功      
  20. });  

写到这里,我不得不吐槽一下《javascript异步编程》这本书的翻译者,将这一部分翻译的非常僵硬,一个很简单的原理用一些很难理解的词汇来解释。

6、管道接口

从jQuery 1.8开始的版本中开始废弃掉 pipe 接口,而改为用then方法替代它,then接口支持三个参数:deferred.then( doneFilter [, failFilter ] [, progressFilter ] ) ,这样可以支持复杂的链式调用,示例如下:

JavaScript代码
  1.     
  2. var request = $.ajax(url, { dataType:"json" } ),      
  3.     chained = request.then(function( data ) {      
  4.         return $.ajax( url2, {data:{user: data.userId}});      
  5.     });      
  6. chained.done(function( data ) {      
  7.      // data retrieved from url2 as provided by the first request      
  8. });  

这太简单了,那能不能利用then方法做一些更复杂的逻辑呢?当然可以,示例如下:

JavaScript代码
  1.     
  2. var username = "test";              
  3. var password = "123456";                    
  4. $.getJSON("user.json").then(function (data) {                      
  5.     if(checkUser(data, username, password)) {                        
  6.         return $.getJSON("status.json");    //校验通过,声明一个新的Promise对象到then方法中进行二次校验                 
  7.     }else{                                
  8.         var def = $.Deferred();                  
  9.         def.reject("用户名或者密码不正确");  //用户名校验错误,抛出Deferred对象,执行最后的fail方法                              
  10.         return def;                                    
  11.     }      
  12. },function(data){   //如果user.json请求发生异常,执行这里,并将错误处理抛给最后绑定的fail方法,这里也可以不做处理,直接响应最后的fail方法      
  13.     var def = $.Deferred();      
  14.     def.reject("请求user.json失败");      
  15.     return def;      
  16. }).then(function (data) {        
  17.     if(checkStatus(data, username)) {        
  18.         return username;//返回非Promise或者Deferred对象,则当做已经resolved的Deferred对象并将该值作为参数执行done方法              
  19.     } else {                    
  20.         var def = $.Deferred();              
  21.         def.reject("用户已经被停用!请与管理员联系!");                  
  22.         return def;                    
  23.     }}).done(function (data) {         
  24.         alert("欢迎你" + data +"登陆我们的系统!");      
  25.     }).fail(function (data) {                
  26.         alert(data);      
  27.     });       
  28.                
  29. function checkUser(){      
  30.   return false;      
  31. }      
  32. function checkStatus(){      
  33.   return true;      
  34. }  

关于这Promise和Deferred对象的使用方法基本如此,总的来说Promise和Deferred对象给我们提供了一种比较新颖的异步编程解耦方案,如果条件允许可以实践中多多应用~~


转载自:http://www.chauvetxiao.com/bo-blog/read.php?33

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值