jquery 中的deffered和promise对象方法及$when done then的使用

对于$.ajax请求来说,如果层级比较多,程序看起来会比较乱,而为了解决这种问题,才有了$when...done...fail...then的封装,它将$.ajax这嵌套结构转成了顺序平行的结果,向下面的$.ajax写法,看起来很乱

[javascript]  view plain  copy
  1. $.ajax({  
  2. url: "/home/GetProduct",  
  3. dataType: "JSON",  
  4. type: "GET",  
  5. success: function (data) {  
  6. $.ajax({  
  7. url: "/home/GetProduct",  
  8. dataType: "JSON",  
  9. type: "GET",  
  10. success: function (data) {  
  11. $.ajax({  
  12. url: "/home/GetProduct",  
  13. dataType: "JSON",  
  14. type: "GET",  
  15. success: function (data) {  
  16. }  
  17. }  
  18. }  
而它实现的功能无非就是外层执行完成后,去执行内层的代码代码,看下面的$.when写法,就清晰多了
[javascript]  view plain  copy
  1. $.when($.ajax({  
  2. url: "/home/GetProduct",  
  3. dataType: "JSON",  
  4. type: "GET",  
  5. success: function (data) {  
  6. alert(JSON.stringify(data));  
  7. }  
  8. })).done(function (data) {  
  9. alert(data[0].Name);  
  10. }).done(function (data) {  
  11. alert(data[1].Name);  
  12. }).fail(function () {  
  13. alert("程序出现错误!");  
  14. }).then(function (data) {  
  15. alert("程序执行完成");  
  16. });  
而对于这种ajax的封装,在比较流行的node.js里也需要被看到,这就类似于方法的回调技术

下面通过一个例子再给大家介绍jquery when then(done) 用法

//运行条件jquery 1.82以上,直接运行代码,看结果

[javascript]  view plain  copy
  1. var log = function(msg){   
  2. window.console && console.log(msg)   
  3. }   
  4. function asyncThing1(){   
  5. var dfd = $.Deferred();   
  6. setTimeout(function(){   
  7. log('asyncThing1 seems to be done...');   
  8. dfd.resolve('1111');   
  9. },1000);   
  10. return dfd.promise();   
  11. }   
  12. function asyncThing2(){   
  13. var dfd = $.Deferred();   
  14. setTimeout(function(){   
  15. log('asyncThing2 seems to be done...');   
  16. dfd.resolve('222');   
  17. },1500);   
  18. return dfd.promise();   
  19. }   
  20. function asyncThing3(){   
  21. var dfd = $.Deferred();   
  22. setTimeout(function(){   
  23. log('asyncThing3 seems to be done...');   
  24. dfd.resolve('333');   
  25. },2000);   
  26. return dfd.promise();   
  27. }   
  28. /* do it */  
  29. $.when( asyncThing1(), asyncThing2(), asyncThing3() ).done(function(res1, res2, res3){   
  30. log('all done!');   
  31. log(res1 + ', ' + res2 + ', ' + res3);   
  32. })  

如果AJAX请求之间存在依赖关系,我们的代码就会形成Pyramid of Doom(金字塔厄运)。比如我们要完成这样一件事:有4个供Ajax访问的url地址,需要先Ajax访问第1个,在第1个访问完成后,用拿到的返回数据作为参数再访问第2个,第2个访问完成后再第3个...以此到4个全部访问完成。按照这样的写法,似乎会变成这样:
[javascript]  view plain  copy
  1. $.ajax({    
  2.     url: url1,    
  3.     success: function(data){    
  4.         $.ajax({    
  5.             url: url2,    
  6.             data: data,    
  7.             success: function(data){    
  8.                 $.ajax({    
  9.                     //...    
  10.                 });    
  11.             }        
  12.         });    
  13.     }    
  14. });  
1.Deferred.then()相当于Deferred.done()、Deferred.fail()、Deferred.progress()的合体,可以同时注册3个状态下的回调函数。
[javascript]  view plain  copy
  1. function success(data)  
  2. {  
  3.     alert(success data =  + data);  
  4. }  
  5.    
  6. function fail(data)  
  7. {  
  8.     alert(fail data =  + data);  
  9. }  
  10.    
  11. function progress(data)  
  12. {  
  13.     alert(progress data =  + data);  
  14. }  
  15.    
  16. var deferred = $.Deferred();  
  17.    
  18. // 一起注册回调  
  19. deferred.then(success, fail, progress);  
  20.    
  21. // 分别注册回调  
  22. deferred.done(success);  
  23. deferred.fail(fail);  
  24. deferred.progress(progress);  
  25.    
  26. deferred.notify(10%);  
  27. deferred.resolve(ok);  
当然我们也可以像done()一样,多次调用then()注册回调函数。then()虽然可以这么使用,但是实际开发中一般不这么用,因为没有啥必要。JQuery1.8之前,这就是then()方法的作用。


2.Deferred.then()解决多个异步操作之间有依赖的问题,这才是then()真正有意义的场景。JQuery1.8之后,then()取代了过时的pipe()方法。这种场景下,我们需要使用Deferred.then()返回的新Promise对象。上面的第一种使用方式,我们忽略了Deferred.then()的返回值。

[javascript]  view plain  copy
  1. var deferred = $.Deferred();  
  2.    
  3. // 使用then()注册一个resolved状态的回调函数,并返回一个过滤后的promise  
  4. // 返回的filtered已经不是原来的Deferred或者Promise对象了  
  5. var filtered = deferred.then(function( value ) {  
  6.                 alert(trigger Deferred filter.value=+value);//5  
  7.                 return value * 2;  
  8.             });  
  9.    
  10. // 用过滤后的Promise再次注册回调函数           
  11. filtered.done(function( value ) {  
  12.     alert(filtered value= + value);//10  
  13. });  
  14.     
  15. deferred.resolve( 5 );  
我们用deferred.then()注册了一个完成状态下的回调函数,这个回调函数得到的值是5;之后用filtered这个新的Promise注册回调函数,这个回调函数中得到的值是10(第一个回调函数的返回结果)。
我们知道deferred.resolve()、deferred.reject()、deferred.notify()可以指定参数值,这个参数会传递给相应状态下的回调函数。如果我们使用的是done()、fail()、progress()注册的回调函数,那么某个状态下的所有回调函数得到的都是相同参数。但是如果我们使用了then()注册回调函数,那么第一回调函数的返回值将作为第二个回调函数的参数,同样的第二个函数的返回值是第三个回调函数的参数。可以对比下面的2段代码,体会下done()和then的差别。
[javascript]  view plain  copy
  1. var deferred = $.Deferred();  
  2.    
  3. // done()返回的仍然是原来的Deferred对象  
  4. var done_ret = deferred.done(function(data){  
  5.     alert(data=+data);//5  
  6.     return 2 * data;  
  7. });  
  8. alert(deferred == done_ret);//true  
  9.    
  10. done_ret.done(function(data){  
  11.     alert(data=+data);//5  
  12. });  
  13.    
  14. deferred.resolve( 5 );  
[javascript]  view plain  copy
  1. var deferred = $.Deferred();  
  2.    
  3. // then()返回的是一个新Promise对象  
  4. //then注册的回调函数的返回值将作为这个新Promise的参数  
  5. var then_ret = deferred.then(function(data){  
  6.     alert(data=+data);//5  
  7.     return 2 * data;  
  8. });  
  9. alert(then_ret == deferred);//false  
  10.    
  11. then_ret.done(function(data){  
  12.     alert(data=+data);//10  
  13. });  
  14.    
  15. deferred.resolve( 5 );  
同样地,Deferred.then也能够实现rejected和pending状态的回调函数过滤。
[javascript]  view plain  copy
  1. var defer = $.Deferred();  
  2. var filtered = defer.then( nullfunction( value ) {  
  3.     return value * 3;  
  4.   });  
  5.   defer.reject( 6 );  
  6.  filtered.fail(function( value ) {  
  7.   alert( Value is ( 3*6 = ) 18:  + value );  
  8. });  
下面这段代码可以实现chain tasks,解决异步操作中回调难的问题。
[javascript]  view plain  copy
  1. var defered = $.Deferred();  
  2.  var promise1 = defered.then(function(data){  
  3.     alert(data);//  
  4.     return data+=1;  
  5. });  
  6.  var promise2 = promise1.then(function(data){  
  7.     alert(data);//1  
  8.     return data+=2;  
  9. });  
  10.  var promise3 = promise2.then(function(data){  
  11.     alert(data);//12  
  12.     return data+=3;  
  13. });  
  14.  promise3.done(function(data){  
  15.     alert(data);//123  
  16. });  
  17.  defered.resolve();  
正是由于then()这个特性,我们就可以上面复杂的AJAX嵌套改成如下形式:
[javascript]  view plain  copy
  1. var promise1 = $.ajax(url1);  
  2. var promise2 = promise1.then(function(data){  
  3.     return $.ajax(url2, { data: data });  
  4. });  
  5. var promise3 = promise2.then(function(data){  
  6.     return $.ajax(url3, { data: data });  
  7. });  
  8. promise3.done(function(data){  
  9.     // data retrieved from url3  
  10. });  

二、示例

以前写动画时,我们通常是这么干的:

$('.animateEle').animate({
  opacity:'.5'
}, 4000,function(){
  $('.animateEle2').animate({
    width:'100px'
  },2000,function(){
    // 这样太伤了
    $('.animateEle3').animate({
      height:'0'
    },2000);
  });
});

假如这么使用回调的话,那就太伤了。幸好,还有一些现成的 Promise 解决方案来优雅地解决这种问题。

我们看看 jQuery 提供的解决办法。

var animate1 = function() {
  return $('.animateEle1').animate({opacity:'.5'},4000).promise();
};
var animate2 = function() {
  return $('.animateEle2').animate({width:'100px'},2000).promise();
};
var animate3 = function(){
  return $('.animateEle3').animate({height:'0'},2000).promise();
};
// so easy,有木有,so clear,有木有
$.when(animate1()).then(animate2).then(animate3);

很明显,更改后的代码更易懂易读了。

但是,上面的代码,有些细节的东西并没有透露,一不小心,就容易出错,得不到我们想要的顺序完成动画的效果。下面让我们来全面理解 jQuery 提供的 promise 和deferred 对象的方法,看看到底如何使用。

三、promise和deffered对象方法

promise 对象其实就是 deferred 对象的特例,因为 promise 对象不能更改异步状态,而 deferred 对象可以。这点在他们的方法设计上,有着明显的体现。

1.promise对象方法

通常,对于DOM,动画,ajax相关方法,我们都可以使用 promise 方法。调用 promise 方法,返回的是 promise 对象。可以链式调用 promise 方法。

promise对象常见的方法有三个 : done , fail , then 。

其它的方法就不要去记了, jquery 这里的接口方法太多了,在我看来挺啰嗦的,就跟早期的事件方法绑定一样, live , delegate , bind ,最终不是都归为on 来管了么。

代码示例,如下:

1.DOM使用 promise 方法:

var box=$('#box');
box.promise().done(function(ele){
    console.log(ele);//jQuery box
});

2.Ajax使用 promise 方法(默认返回一个 promise 对象,所以可以不必显式调用 promise 方法):

$.post('/',{}).done(function(data){
    console.log('请求成功');
}).fail(function(){
    console.log('请求错误');
});

动画示例已有,就不重复列出了。

2.deferred对象方法

对于 deferred 对象呢,也就是使用 $.Deferred() 方法,以及 $.when() 等方法创造出来的对象,有如下的常用方法:

  • resolve , reject , notify ;
  • done , fail , progress ;

另外还有 promise 、 then 和 always 方法。

之所以这么排版,是因为他们是对应的,也就是说: resolve 方法会触发 done 的回调执行, reject 会触发 fail 的回调, notify 会触发 progress 的回调。

直接看代码:

var wait = function(ms) {
  var dtd = $.Deferred();
  setTimeout(dtd.resolve, ms);
  // setTimeout(dtd.reject, ms);
  // setTimeout(dtd.notify, ms);
  return dtd.promise(); //此处也可以直接返回dtd
};

wait(2500).done(function() {
  console.log('haha,师太,你可让老衲久等了');
}).fail(function() {
  console.log('失败了');
}).progress(function(res) {
  console.log('等待中...');
});

我们看到了,上面的代码中,在 wait 函数中,返回的是个 promise 对象,而不是deferred 对象。

要知道, promise 对象是没有 resolve , reject , notify 等方法的,也就意味着,你无法针对 promise 对象进行状态更改,只能在 done 或 fail 中进行回调配置。所以,你如果这么调用 wait(2500).resolve() 将会报错,因为 wait(2500) 返回的是个 promise 对象,不存在 resolve 方法。

但是,这么做,有个好处,我们把 dtd 这个 deferred 对象放在了 wai t函数中,作为了局部变量,避免了全局的污染;进一步通过 promise 方法,转化 dtd 这个 deferred 对象为 promise 对象,避免了函数 wait 外部可能发生的状态更改(假如我们确实有这个需求)。

比如:

var wait = function(ms) {
  var dtd = $.Deferred();
  setTimeout(dtd.resolve, ms);
  // setTimeout(dtd.reject, ms);
  // setTimeout(dtd.notify, ms);
  return dtd; //此处也可以直接返回dtd
};

wait(2500).reject().fail(function(){
  console.log('失败了...............');
});

我们在外部更改了 wait 返回的 deferred 对象的状态,这样必然触发该对象的 fail 回调函数。

对于 always 方法,从字面意思上就很容易理解, deferred 对象无论是 resolve还是 reject ,都会触发该方法的回调。

3.其它共性

此处讲讲 then 和 $.when 方法的使用。它们对 promise 对象也适用。

  • $.when 方法接受多个 deferred 对象或者纯javascript对象,返回 promise 对象。
  • then 方法依次接受三个回调,分别为 deferred 对象 resolve , reject , notify 后触发的回调,返回一个 promise 对象。注意,必须传入函数,而该函数只有返回一个 promise 对象,才能够让异步事件按照预期顺序来执行。

我们来看看最开始的动画示例代码, $.when(animate1()).then(animate2).then(animate3) , $.when 方法中接受了一个 animate1 的函数执行结果,也就是得到了一个 promise 对象,而后的 then 中,则只是接受了一个变量名,这样得到的结果是一个匿名的函数体,而该函数中返回的是 promise 对象。正好符合了我们对 then 接受参数的要求。

假如我们把执行语句改成: $.when(animate1()).then(animate2()).then(animate3()) ,这样造成的结果就是三个动画同步执行了。与 $.when(animate1(),animate2(),animate3()) 无异。

既然 then 是如此要求,那么与 then 方法类似的 done , fail , progress 也是一样的。

目前的ES标准中还未支持Promise对象,那么我们就自己动手,丰衣足食吧。思路大致是这样的,用2个数组(doneListfailList)分别存储成功时的回调函数队列和失败时的回调队列

  • state: 当前执行状态,有pendingresolvedrejected3种取值

  • done: 向doneList中添加一个成功回调函数

  • fail: 向failList中添加一个失败回调函数

  • then: 分别向doneListfailList中添加回调函数

  • always: 添加一个无论成功还是失败都会调用的回调函数

  • resolve: 将状态更改为resolved,并触发绑定的所有成功的回调函数

  • reject: 将状态更改为rejected,并触发绑定的所有失败的回调函数

  • when: 参数是多个异步或者延迟函数,返回值是一个Promise兑现,当所有函数都执行成功的时候执行该对象的resolve方法,反之执行该对象的reject方法
    下面是我的具体实现过程:

    [javascript]  view plain  copy
    1. var Promise = function() {  
    2.     this.doneList = [];  
    3.     this.failList = [];  
    4.     this.state = 'pending';  
    5. };  
    6.   
    7. Promise.prototype = {  
    8.     constructor: 'Promise',  
    9.     resolve: function() {  
    10.         this.state = 'resolved';  
    11.         var list = this.doneList;  
    12.         for(var i = 0, len = list.length; i < len; i++) {  
    13.             list[0].call(this);  
    14.             list.shift();  
    15.         }  
    16.     },  
    17.     reject: function() {  
    18.         this.state = 'rejected';  
    19.         var list = this.failList;  
    20.         for(var i = 0, len = list.length; i < len; i++){  
    21.             list[0].call(this);  
    22.             list.shift();  
    23.         }  
    24.     },  
    25.     done: function(func) {  
    26.         if(typeof func === 'function') {  
    27.             this.doneList.push(func);  
    28.         }  
    29.         return this;  
    30.     },  
    31.     fail: function(func) {  
    32.         if(typeof func === 'function') {  
    33.             this.failList.push(func);  
    34.         }  
    35.         return this;  
    36.     },  
    37.     then: function(doneFn, failFn) {  
    38.         this.done(doneFn).fail(failFn);  
    39.         return this;  
    40.     },  
    41.     always: function(fn) {  
    42.         this.done(fn).fail(fn);  
    43.         return this;  
    44.     }  
    45. };  
    46.   
    47. function when() {  
    48.     var p = new Promise();  
    49.     var success = true;  
    50.     var len = arguments.length;  
    51.     for(var i = 0; i < len; i++) {  
    52.         if(!(arguments[i] instanceof Promise)) {  
    53.             return false;  
    54.         }  
    55.         else {  
    56.             arguments[i].always(function() {  
    57.                 if(this.state != 'resolved'){  
    58.                     success = false;  
    59.                 }  
    60.                 len--;  
    61.                 if(len == 0) {  
    62.                     success ? p.resolve() : p.reject();  
    63.                 }  
    64.             });  
    65.         }  
    66.     }  
    67.     return p;  
    68. }  

猜你喜欢

转载自blog.csdn.net/qq_41813695/article/details/80032924