一、从Ajax请求说起
Promise和Deferred对象为我们提供了一种很优雅的异步处理方案,Promise最开始出现于Dojo框架中,09年Kris Zyp有感于dojo.Deferred提出了CommonJS之Promise/A规范,jQuery从1.5版本开始实现了这一重量级方案,不过没有严格按照规范进行实现,有一些API上的差异。说到这里,先来看下这两个对象能够做些什么吧!
从一个ajax请求说起,如果有一个异步的ajax请求,按照一般的实现方式,包括我现在也还在这样做:
- $.get('/myRequest',function(){
- success: onSuccess,
- failure: onFailure,
- always : onAlways
- })
在这种常规的实现方案中,会把异步的回调参数和请求参数写在一起做为ajax的参数,但是如果通过promise的实现方式,这种写法可以转变为这样:
- var promise = $.get("/myRequest");
- promise.done(onSuccess);
- promise.fail(onFailure);
- promise.always(onAlways);
这种方式和普通的调用方式有什么不同的地方呢?
在这种方式中可以和EventEmitter一样可以任意添加done、fail、always的处理方法,最主要的作用还是把请求与回调进行了分离与解耦,通过这样的封装还可以做更多好玩的事,接着往下看吧。
promise对象中的方法只能被动的被执行,如果需要主动触发这些事件怎么办呢,这时我们可以使用Deferred对象,jQuery中可以这样使用(摘自《javascript异步编程》):
- var prompt = new $.Deferered();
- prompt.always(function(){
- console.info("A choice was made:);
- });
- prompt.done(function(){
- console.info("Starting game...");
- });
- prompt.fail(function(){
- console.info("No game tody.");
- });
- $("#playGame").focus.on("keypress",function(e){
- if(e.keyCode === "121"){
- prompt.resolve();
- }else if(e.keyCode == 110){
- prompt.reject();
- }else{
- return false;
- }
- })
这样就可以通过键盘发出不同的指令了,Promise与Deferred对象不同的地方便是Deferred可以通过resolve和reject方法触发对应的回调,Deferred是Promise对象的一个超集,可以通过Deferred对象的promise方法获取对应的Promise对象,说到这里,有一个很关键的名词 -state(状态),在对象内部会维持这样一个状态值的变化,这样的状态控制机制使得done、fail 方法只会被响应一次:
二、jQuery中使用promise和deferred对象
promise和deferred对象被发扬光大一定程度上是由于在jQuery中得到支持,这里简单介绍一些jQuery中的主要用法:
1、promise对象的链式调用
- $.ajax("test.cgi").
- done(function(){console.info("called first time");}).
- fail(function(){console.info("error");}).
- done(function(){console.info("called second time")});
2、处理动画回调
- var $flash = $("#flash");
- var showpromise = $flash.fadeIn().promise();
- var hidePromise = $flash.fadeOut().promise();
- showpromise.done(function(){console.info("fadeIn call back")});
- hidePromise.done(function(){console.info("fadeOut call back")})
通过promise对象把动画与回调进行了很好的解耦,无需再把callback以参数的形式进行传递,这只是jQuery在1.6以及1.7中的权宜之计,使用Deferred对象也可以达到同样的效果
- var $flash = $("#flash");
- var defer = new $.Deferred();
- $flash.fadeOut(defer.resolve);
- defer.done(function(){console.info("fadeIn call back")});
3、如何给回调传递参数
- var def = new $.Deferred();
- def.done(function(name){
- console.info("done with name %s",name);
- });
- def.resolve("test");
- //也可以传入上下文对象
- def.resolveWith(context,"test");
- //也可以这样调用
- def.resolve.call(context,"test");
4、进度通知
Promise主要提供对结果的监控,在jQuery中也可以通过progress事件对过程进行处理
- var def = new $.Deferred();
- def.progress(function(count){
- console.info(count);
- });
- def.notify(1);
- def.notify(2);
- ...
5、对多个异步事件指定回调
- $.when($.post("/1",data1),$.post("/2",data2)).
- then(onSuccess,onFailure);
当两个post请求结束时才会执行then方法,then方法的第一个参数做为done的回调,第二个参数作为failure的回调,如果只有一个参数则作为done的回调方法。那如何处理每一个Promise对象的参数呢?每一个Promise对象返回的结果会按照顺序传给done方法,所以我们可以这样使用:
- $.when($.post("/1",data1),$.post("/2",data2)).
- done(function(post1_Args,post2_Args){
- //...
- });
但是这样写法不是很被推荐,可读性较好的写法可以这样处理多个promise对象的结果:
- var data = {};
- var getting1 = $.get("/1").
- done(function(result){data["1"] = result;});
- var getting2 = $.get("/2").
- done(function(result){data["2"]= result;});
- $.when(getting1,getting2).done(function(){
- //此处访问data已经拿到两个promise对象返回的数据
- });
如果$.when传入的参数不是Promise或者Deferred对象,则会视为一个已经resolved的Deferred对象并立即执行,如下所示:
- $.when({test:"123"}).done(function(data){
- console.info(data.test); // ==> 123
- })
- //或者这样写
- $.when(test()).done(function(data){
- console.info(data.test); // ==> 123
- })
- function test(){
- return {test: "123"}
- }
基于when方法这样的特性,如果when方法的参数是function并且返回的Deferred对象,则可以做这样一些异步的处理:
- var wait1 = function(){
- var def = $.Deferred();
- var tasks = function(){
- def.resolve("方法1执行成功");
- };
- setTimeout(tasks,1000);
- return def;
- }
- var wait2 = function(){
- var def = $.Deferred();
- var tasks = function(){
- def.resolve("方法2执行成功");
- };
- setTimeout(tasks,2000);
- return def;
- }
- $.when(wait1(),wait2()).done(function(data1,data2){
- alert(data1 + "," + data2) // ==> 方法1执行成功,方法2执行成功
- });
写到这里,我不得不吐槽一下《javascript异步编程》这本书的翻译者,将这一部分翻译的非常僵硬,一个很简单的原理用一些很难理解的词汇来解释。
6、管道接口
从jQuery 1.8开始的版本中开始废弃掉 pipe 接口,而改为用then方法替代它,then接口支持三个参数:deferred.then( doneFilter [, failFilter ] [, progressFilter ] ) ,这样可以支持复杂的链式调用,示例如下:
- var request = $.ajax(url, { dataType:"json" } ),
- chained = request.then(function( data ) {
- return $.ajax( url2, {data:{user: data.userId}});
- });
- chained.done(function( data ) {
- // data retrieved from url2 as provided by the first request
- });
这太简单了,那能不能利用then方法做一些更复杂的逻辑呢?当然可以,示例如下:
- var username = "test";
- var password = "123456";
- $.getJSON("user.json").then(function (data) {
- if(checkUser(data, username, password)) {
- return $.getJSON("status.json"); //校验通过,声明一个新的Promise对象到then方法中进行二次校验
- }else{
- var def = $.Deferred();
- def.reject("用户名或者密码不正确"); //用户名校验错误,抛出Deferred对象,执行最后的fail方法
- return def;
- }
- },function(data){ //如果user.json请求发生异常,执行这里,并将错误处理抛给最后绑定的fail方法,这里也可以不做处理,直接响应最后的fail方法
- var def = $.Deferred();
- def.reject("请求user.json失败");
- return def;
- }).then(function (data) {
- if(checkStatus(data, username)) {
- return username;//返回非Promise或者Deferred对象,则当做已经resolved的Deferred对象并将该值作为参数执行done方法
- } else {
- var def = $.Deferred();
- def.reject("用户已经被停用!请与管理员联系!");
- return def;
- }}).done(function (data) {
- alert("欢迎你" + data +"登陆我们的系统!");
- }).fail(function (data) {
- alert(data);
- });
- function checkUser(){
- return false;
- }
- function checkStatus(){
- return true;
- }
关于这Promise和Deferred对象的使用方法基本如此,总的来说Promise和Deferred对象给我们提供了一种比较新颖的异步编程解耦方案,如果条件允许可以实践中多多应用~~
转载自:http://www.chauvetxiao.com/bo-blog/read.php?33