javascript异步处理与Jquery的deferred对象总结

实例

promise()返回的promise对象,它相当于Deferred对象的副本,不允许开发者通过promise对象修改Deferred对象状态

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script type="text/javascript" src="../js/jquery-1.9.1.min.js"></script>
</head>
<body>
    <div id="show"> </div>
    <div> </div>
    
<script type="text/javascript">
    
    $(function(){
       /* 
       //$.ajax() 返回一个deferred对象 (1.5版本后)
       $.ajax({
            url : "html1.html",
            dataType : "html"     
       }).done(function(data, textStatus){
           alert("done: " + data);
       }).fail(function(XMLHttpRequest, textStatus, errorThrown){
           alert("fail:" + textStatus + ", " + errorThrown);
       });    */
       
       /*
       //如果使用 $.when 可以传入多个参数, 并接受多个参数
       $.when(
            $.ajax("html1.html", {dataType:"html"}),
            $.ajax("html1.html", {dataType:"html"})                 
       ).done(function(data1, data2){
           alert(data1);
           alert(data2);
       }).fail(function(){
           alert("fail");
       });     */
      
       /*
       //如果是一个耗时函数,可以通过deferred来管理函数
       function wait(){
           var defer = $.Deferred();
           setTimeout(function(){
               alert("invoke");
               //defer.resolve("ok");  //会调用done
               defer.rejectWith($("#show"), ["i am failed"]); //会调用fail 第一个参数是上下文对象
           }, 1000);
           return defer.promise();          
       }
       wait().done(function(data){
           alert("done: " + data);
       }).fail(function(data){
           alert("fail");
           this.append(data); 
       });    */
      
       //通过deferred对象的promise方法可以为一般对象增加deferred接口
       var obj = {
           getname : function(){
               alert("your name is kevin");
           }
       };
       var defer = $.Deferred();
       defer.promise(obj);
       obj.done(function(data){
           alert("obj done " + data);
       }).fail(function(data){
           alert("obj fail " + data);
       });
       //defer.resolve("xxx"); //调obj done
       defer.reject("xxx"); //调obj fail
           
      
    });
            
</script>
</body>
</html>

----------------------------------------

jQuery的deferred对象详解

http://www.ruanyifeng.com/blog/2011/08/a_detailed_explanation_of_jquery_deferred_object.html

作者: 阮一峰

日期: 2011年8月16日

jQuery的开发速度很快,几乎每半年一个大版本,每两个月一个小版本。

每个版本都会引入一些新功能。今天我想介绍的,就是从jQuery 1.5.0版本开始引入的一个新功能----deferred对象

这个功能很重要,未来将成为jQuery的核心方法,它彻底改变了如何在jQuery中使用ajax。为了实现它,jQuery的全部ajax代码都被改写了。但是,它比较抽象,初学者很难掌握,网上的教程也不多。所以,我把自己的学习笔记整理出来了,希望对大家有用。

本文不是初级教程,针对的读者是那些已经具备jQuery使用经验的开发者。如果你想了解jQuery的基本用法,请阅读我编写的《jQuery设计思想》《jQuery最佳实践》

======================================

jQuery的deferred对象详解

作者:阮一峰

一、什么是deferred对象?

开发网站的过程中,我们经常遇到某些耗时很长的javascript操作。其中,既有异步的操作(比如ajax读取服务器数据),也有同步的操作(比如遍历一个大型数组),它们都不是立即能得到结果的。

通常的做法是,为它们指定回调函数(callback)。即事先规定,一旦它们运行结束,应该调用哪些函数。

但是,在回调函数方面,jQuery的功能非常弱。为了改变这一点,jQuery开发团队就设计了deferred对象

简单说,deferred对象就是jQuery的回调函数解决方案。在英语中,defer的意思是"延迟",所以deferred对象的含义就是"延迟"到未来某个点再执行。

它解决了如何处理耗时操作的问题,对那些操作提供了更好的控制,以及统一的编程接口。它的主要功能,可以归结为四点。下面我们通过示例代码,一步步来学习。

二、ajax操作的链式写法

首先,回顾一下jQuery的ajax操作的传统写法:

  $.ajax({

    url: "test.html",

    success: function(){
      alert("哈哈,成功了!");
    },

    error:function(){
      alert("出错啦!");
    }

  });

(运行代码示例1

在上面的代码中,$.ajax()接受一个对象参数,这个对象包含两个方法:success方法指定操作成功后的回调函数,error方法指定操作失败后的回调函数。

$.ajax()操作完成后,如果使用的是低于1.5.0版本的jQuery,返回的是XHR对象,你没法进行链式操作;如果高于1.5.0版本,返回的是deferred对象,可以进行链式操作。

现在,新的写法是这样的:

  $.ajax("test.html")

  .done(function(){ alert("哈哈,成功了!"); })

  .fail(function(){ alert("出错啦!"); });

(运行代码示例2

可以看到,done()相当于success方法,fail()相当于error方法。采用链式写法以后,代码的可读性大大提高。

三、指定同一操作的多个回调函数

deferred对象的一大好处,就是它允许你自由添加多个回调函数。

还是以上面的代码为例,如果ajax操作成功后,除了原来的回调函数,我还想再运行一个回调函数,怎么办?

很简单,直接把它加在后面就行了。

  $.ajax("test.html")

  .done(function(){ alert("哈哈,成功了!");} )

  .fail(function(){ alert("出错啦!"); } )

  .done(function(){ alert("第二个回调函数!");} );

(运行代码示例3

回调函数可以添加任意多个,它们按照添加顺序执行。

四、为多个操作指定回调函数

deferred对象的另一大好处,就是它允许你为多个事件指定一个回调函数,这是传统写法做不到的。

请看下面的代码,它用到了一个新的方法$.when()

  $.when($.ajax("test1.html"), $.ajax("test2.html"))

  .done(function(){ alert("哈哈,成功了!"); })

  .fail(function(){ alert("出错啦!"); });

(运行代码示例4

这段代码的意思是,先执行两个操作$.ajax("test1.html")和$.ajax("test2.html"),如果都成功了,就运行done()指定的回调函数;如果有一个失败或都失败了,就执行fail()指定的回调函数。

五、普通操作的回调函数接口(上)

deferred对象的最大优点,就是它把这一套回调函数接口,从ajax操作扩展到了所有操作。也就是说,任何一个操作----不管是ajax操作还是本地操作,也不管是异步操作还是同步操作----都可以使用deferred对象的各种方法,指定回调函数。

我们来看一个具体的例子。假定有一个很耗时的操作wait:

  var wait = function(){

    var tasks = function(){

      alert("执行完毕!");

    };

    setTimeout(tasks,5000);

  };

我们为它指定回调函数,应该怎么做呢?

很自然的,你会想到,可以使用$.when():

  $.when(wait())

  .done(function(){ alert("哈哈,成功了!"); })

  .fail(function(){ alert("出错啦!"); });

(运行代码示例5

但是,这样写的话,done()方法会立即执行,起不到回调函数的作用。原因在于$.when()的参数只能是deferred对象,所以必须对wait()进行改写:

  var dtd = $.Deferred(); // 新建一个deferred对象

  var wait = function(dtd){

    var tasks = function(){

      alert("执行完毕!");

      dtd.resolve(); // 改变deferred对象的执行状态

    };

    setTimeout(tasks,5000);

    return dtd;

  };

现在,wait()函数返回的是deferred对象,这就可以加上链式操作了。

  $.when(wait(dtd))

  .done(function(){ alert("哈哈,成功了!"); })

  .fail(function(){ alert("出错啦!"); });

(运行代码示例6

wait()函数运行完,就会自动运行done()方法指定的回调函数。

六、deferred.resolve()方法和deferred.reject()方法

如果仔细看,你会发现在上面的wait()函数中,还有一个地方我没讲解。那就是dtd.resolve()的作用是什么?

要说清楚这个问题,就要引入一个新概念"执行状态"。jQuery规定,deferred对象有三种执行状态----未完成,已完成和已失败。如果执行状态是"已完成"(resolved),deferred对象立刻调用done()方法指定的回调函数;如果执行状态是"已失败",调用fail()方法指定的回调函数;如果执行状态是"未完成",则继续等待,或者调用progress()方法指定的回调函数(jQuery1.7版本添加)。

前面部分的ajax操作时,deferred对象会根据返回结果,自动改变自身的执行状态;但是,在wait()函数中,这个执行状态必须由程序员手动指定。dtd.resolve()的意思是,将dtd对象的执行状态从"未完成"改为"已完成",从而触发done()方法。

类似的,还存在一个deferred.reject()方法,作用是将dtd对象的执行状态从"未完成"改为"已失败",从而触发fail()方法。

  var dtd = $.Deferred(); // 新建一个Deferred对象

  var wait = function(dtd){

    var tasks = function(){

      alert("执行完毕!");

      dtd.reject(); // 改变Deferred对象的执行状态

    };

    setTimeout(tasks,5000);

    return dtd;

  };

  $.when(wait(dtd))

  .done(function(){ alert("哈哈,成功了!"); })

  .fail(function(){ alert("出错啦!"); });

(运行代码示例7

七、deferred.promise()方法

上面这种写法,还是有问题。那就是dtd是一个全局对象,所以它的执行状态可以从外部改变。

请看下面的代码:

  var dtd = $.Deferred(); // 新建一个Deferred对象

  var wait = function(dtd){

    var tasks = function(){

      alert("执行完毕!");

      dtd.resolve(); // 改变Deferred对象的执行状态

    };

    setTimeout(tasks,5000);

    return dtd;

  };

  $.when(wait(dtd))

  .done(function(){ alert("哈哈,成功了!"); })

  .fail(function(){ alert("出错啦!"); });

  dtd.resolve();

(运行代码示例8

我在代码的尾部加了一行dtd.resolve(),这就改变了dtd对象的执行状态,因此导致done()方法立刻执行,跳出"哈哈,成功了!"的提示框,等5秒之后再跳出"执行完毕!"的提示框。

为了避免这种情况,jQuery提供了deferred.promise()方法。它的作用是,在原来的deferred对象上返回另一个deferred对象,后者只开放与改变执行状态无关的方法(比如done()方法和fail()方法),屏蔽与改变执行状态有关的方法(比如resolve()方法和reject()方法),从而使得执行状态不能被改变。

请看下面的代码:

  var dtd = $.Deferred(); // 新建一个Deferred对象

  var wait = function(dtd){

    var tasks = function(){

      alert("执行完毕!");

      dtd.resolve(); // 改变Deferred对象的执行状态

    };

    setTimeout(tasks,5000);

    return dtd.promise(); // 返回promise对象

  };

  var d = wait(dtd); // 新建一个d对象,改为对这个对象进行操作

  $.when(d)

  .done(function(){ alert("哈哈,成功了!"); })

  .fail(function(){ alert("出错啦!"); });

  d.resolve(); // 此时,这个语句是无效的

(运行代码示例9

在上面的这段代码中,wait()函数返回的是promise对象。然后,我们把回调函数绑定在这个对象上面,而不是原来的deferred对象上面。这样的好处是,无法改变这个对象的执行状态,要想改变执行状态,只能操作原来的deferred对象。

不过,更好的写法是allenm所指出的,将dtd对象变成wait()函数的内部对象。

  var wait = function(dtd){

    var dtd = $.Deferred(); //在函数内部,新建一个Deferred对象

    var tasks = function(){

      alert("执行完毕!");

      dtd.resolve(); // 改变Deferred对象的执行状态

    };

    setTimeout(tasks,5000);

    return dtd.promise(); // 返回promise对象

  };

  $.when(wait())

  .done(function(){ alert("哈哈,成功了!"); })

  .fail(function(){ alert("出错啦!"); });

(运行代码示例10

八、普通操作的回调函数接口(中)

另一种防止执行状态被外部改变的方法,是使用deferred对象的建构函数$.Deferred()。

这时,wait函数还是保持不变,我们直接把它传入$.Deferred():

  $.Deferred(wait)

  .done(function(){ alert("哈哈,成功了!"); })

  .fail(function(){ alert("出错啦!"); });

(运行代码示例11

jQuery规定,$.Deferred()可以接受一个函数名(注意,是函数名)作为参数,$.Deferred()所生成的deferred对象将作为这个函数的默认参数。

九、普通操作的回调函数接口(下)

除了上面两种方法以外,我们还可以直接在wait对象上部署deferred接口。

  var dtd = $.Deferred(); // 生成Deferred对象

  var wait = function(dtd){

    var tasks = function(){

      alert("执行完毕!");

      dtd.resolve(); // 改变Deferred对象的执行状态

    };

    setTimeout(tasks,5000);

  };

  dtd.promise(wait);

  wait.done(function(){ alert("哈哈,成功了!"); })

  .fail(function(){ alert("出错啦!"); });

  wait(dtd);

(运行代码示例12

这里的关键是dtd.promise(wait)这一行,它的作用就是在wait对象上部署Deferred接口。正是因为有了这一行,后面才能直接在wait上面调用done()和fail()。

十、小结:deferred对象的方法

前面已经讲到了deferred对象的多种方法,下面做一个总结:

  (1) $.Deferred() 生成一个deferred对象。

  (2) deferred.done() 指定操作成功时的回调函数

  (3) deferred.fail() 指定操作失败时的回调函数

  (4) deferred.promise() 没有参数时,返回一个新的deferred对象,该对象的运行状态无法被改变;接受参数时,作用为在参数对象上部署deferred接口。

  (5) deferred.resolve() 手动改变deferred对象的运行状态为"已完成",从而立即触发done()方法。

  (6)deferred.reject() 这个方法与deferred.resolve()正好相反,调用后将deferred对象的运行状态变为"已失败",从而立即触发fail()方法。

  (7) $.when() 为多个操作指定回调函数。

除了这些方法以外,deferred对象还有二个重要方法,上面的教程中没有涉及到。

  (8)deferred.then()

有时为了省事,可以把done()和fail()合在一起写,这就是then()方法。

  $.when($.ajax( "/main.php" ))

  .then(successFunc, failureFunc );

如果then()有两个参数,那么第一个参数是done()方法的回调函数,第二个参数是fail()方法的回调方法。如果then()只有一个参数,那么等同于done()。

  (9)deferred.always()

这个方法也是用来指定回调函数的,它的作用是,不管调用的是deferred.resolve()还是deferred.reject(),最后总是执行。

  $.ajax( "test.html" )

  .always( function() { alert("已执行!");} );

(致谢:本文第一稿发表后,allenm来信指出原文对promise()的理解是错的。现在的第二稿是根据他的文章修改的,在此我表示衷心感谢。)

(完)



-----------------------------------------

http://ju.outofmemory.cn/entry/55151

这是项目组老大整理的一些关于jquery 异步处理请求,以及使用 jquery deferred 对象的一些常见方法。虽然是项目上总结出来的。但也比较通用,分享在这里。

•所有的Ajax操作都采用异步处理。
•采用Jquery的Deffered对象来处理异步调用。
•因为是异步调用,所以$.Ajax函数的返回值不代表返回的结果,只是一个Deffered对象。
•Ajax调用完成后执行的逻辑可以写成函数作为参数传递给Deffered对象的done(), fail(), always()函数来执行。
•如果一个函数包含Ajax调用,那么这个函数必须将Ajax返回的异步对象作为自己的返回值,否则函数的调用者无法保证后续代码的正常执行顺序。

例如:

function readData(){
    $.ajax({ url:"test", dataType:"json" })
    .done(function() {
        //....
    });
}
readData();
//...这里想添加一些后续处理,但程序将在Ajax回调前执行,所以无法达到预期目的
正确的代码:
function readData(){
    return $.ajax({ url:"test", dataType:"json" })
    .done(function() {
        //....
    });
}
readData().done(function () {
    //...想添加的后续处理可以加在这里处理
});

•如果需要调用多个Ajax请求,请注意Ajax请求是否可以同时进行,如果可以应该使用when()函数来同时执行,以提高程序的运行效率和可读性。

•deferred对象有一个方法promise(),可以阻止其他代码修改deferred对象的状态,也就是其他代码调用reslove()和reject()无效。

  var dtd = $.Deferred(); // 新建一个Deferred对象
  var wait = function(dtd){
      var tasks = function(){
          alert("执行完毕!");
          dtd.resolve(); // 改变Deferred对象的执行状态
      };
      setTimeout(tasks,5000);
      return dtd;
  };
  wait(dtd)
  .done(function(){ alert("成功了!"); })
  .fail(function(){ alert("出错啦!"); });
  dtd.resolve();   //这里修改了dtd对象的状态,导致立刻出现“成功了!”的提示
  
正确的例子:
  var dtd = $.Deferred(); // 新建一个Deferred对象
  var wait = function(dtd){
      var tasks = function(){
          alert("执行完毕!");
          dtd.resolve(); // 改变Deferred对象的执行状态
      };
      setTimeout(tasks,5000);
      return dtd.promise(); // 返回promise对象
  };
  wait(dtd)
  .done(function(){ alert("成功了!"); })
  .fail(function(){ alert("出错啦!"); });
  dtd.resolve();   //这里修改dtd对象的状态无效

•一些情况的处理:

1.嵌套异步操作的处理。

如果一个函数内部执行异步任务,并且在异步任务的done回调内会嵌套另一个异步任务,那么简单返回第一个异步任务的返回值是不行的,需要另外自行定义一个Deferred对象作为返回值。
function loadComponent(id){
    var dtd = $.Deferred();
    //loadScript将异步加载一个js文件,所以返回值是一个Deffered对象
    return loadScript(id)
    .done(function() {
        //js加载后,触发component的load事件,此事件内将使用Ajax获取页面模板和数据,生成最终的html
        app.getComponent(id).trigger("load")
       .done( function(){
            dtd.reslove(agruments);
        }).fail( function(){
            dtd.reject(agruments);
        });
    });
    //返回自定义的dtd对象,才能保证返回值的done回调在load事件完成后执行
    return dtd;
}

2.一个函数内同时包含同步和异步case的处理。

/* 这个函数返回一个异步对象,当异步对象执行done时,表示component被加载完成 */
function requireComponent(id){
    if (this.components[id]){
        //如果component已经被加载,直接返回一个reslove的异步对象,否则返回值内容与else分支不同,后续代码难以处理
        var dtd = $.Deferred();
        dtd.reslove(this.getComponent(id));
        return dtd;
    }
    else{
        //如果component未被加载,loadScript将异步加载JavaScript并执行,然后将被加载的component放入this.components
        return loadScript(id);
    }
}

•Jquery异步模型有一个缺点,就是缺乏类似wait操作的处理能力,如果多个异步调用要顺序发生时,就要在done里面嵌套处理,影响代码可读性.

aSyncTask1.done( function () {
    //...第一个操作完成后的处理
    asyncTask2.done( function () {
        //...第二个操作完成后的处理
        asyncTask3.done( function () {
            //...第三个操作完成后的处理
        });
    })
})



-------------------------------------------

http://www.cnblogs.com/lhb25/archive/2013/03/05/jquery-deferred-promise.html

使用 jQuery Deferred 和 Promise 创建响应式应用程序

  这篇文章,我们一起探索一下 JavaScript 中的 Deferred 和 Promise 的概念,它们是 JavaScript 工具包(如Dojo和MochiKit)中非常重要的一个功能,最近也首次亮相于 流行的 JavaScript 库 jQuery(已经是1.5版本的事情了)。 Deferred 提供了一个抽象的非阻塞的解决方案(如 Ajax 请求的响应),它创建一个 “promise” 对象,其目的是在未来某个时间点返回一个响应。如果您之前没有接触过 “promise”,我们将会在下面做详细介绍。

您可能感兴趣的相关文章

 

  抽象来说,deferreds 可以理解为表示需要长时间才能完成的耗时操作的一种方式,相比于阻塞式函数它们是异步的,而不是阻塞应用程序等待其完成然后返回结果。deferred对 象会立即返回,然后你可以把回调函数绑定到deferred对象上,它们会在异步处理完成后被调用。

Promise

  你可能已经阅读过一些关于promise和deferreds实现细节的资料。在本章节中,我们大致介绍下promise如何工作,这些在几乎所有的支持deferreds的javascript框架中都是适用的。

  一般情况下,promise作为一个模型,提供了一个在软件工程中描述延时(或将来)概念的解决方案。它背后的思想我们已经介绍过:不是执行一个方法然后阻塞应用程序等待结果返回,而是返回一个promise对象来满足未来值。

  举一个例子会有助于理解,假设你正在建设一个web应用程序, 它很大程度上依赖第三方api的数据。那么就会面临一个共同的问题:我们无法获悉一个API响应的延迟时间,应用程序的其他部分可能会被阻塞,直到它返回 结果。Deferreds 对这个问题提供了一个更好的解决方案,它是非阻塞的,并且与代码完全解耦 。

  Promise/A提议’定义了一个’then‘方法来注册回调,当处理函数返回结果时回调会执行。它返回一个promise的伪代码看起来是这样的:

  1. promise = callToAPI( arg1, arg2, ...);   
  2. promise.then(function( futureValue ) {   
  3. /* handle futureValue */   
  4. });   
  5. promise.then(function( futureValue ) {  
  6.  /* do something else */  
  7.  });  

  此外,promise回调会在处于以下两种不同的状态下执行:

  • resolved:在这种情况下,数据是可用
  • rejected:在这种情况下,出现了错误,没有可用的值

  幸运的是,'then'方法接受两个参数:一个用于promise得到了解决(resolved),另一个用于promise拒绝(rejected)。让我们回到伪代码:

  1. promise.then( function( futureValue ) {   
  2. /* we got a value */   
  3. } , function() {   
  4. /* something went wrong */  
  5.  } );  

  在某些情况下,我们需要获得多个返回结果后,再继续执行应用程序(例如,在用户可以选择他们感兴趣的选项前,显示一组动态的选项)。这种情况下,'when'方法可以用来解决所有的promise都满足后才能继续执行的场景。

  1. when(   
  2.   promise1,   
  3.   promise2,  
  4.    ...  
  5.  ).then(function( futureValue1, futureValue2, ... ) {   
  6.   /* all promises have completed and are resolved */  
  7.  });  

  一个很好的例子是这样一个场景,你可能同时有多个正在运行的动画。 如果不跟踪每个动画执行完成后的回调,很难做到在动画完成后执行下一步任务。然而使用promise和‘when’方式却可以很直截了当的表示: 一旦动画执行完成,就可以执行下一步任务。最终的结果是我们可以可以简单的用一个回调来解决多个动画执行结果的等待问题。 例如:

  1. when( function(){   
  2. /* animation 1 */   
  3. /* return promise 1 */  
  4. }, function(){  
  5.  /* animation 2 */   
  6. /* return promise 2 */   
  7. } ).then(function(){  
  8.  /* once both animations have completed we can then run our additional logic */   
  9. });  

  这意味着,基本上可以用非阻塞的逻辑方式编写代码并异步执行。 而不是直接将回调传递给函数,这可能会导致紧耦合的接口,通过promise模式可以很容易区分同步和异步的概念。

   在下一节中,我们将着眼于jQuery实现的deferreds,你可能会发现它明显比现在所看到的promise模式要简单。

jQuery的Deferreds

  jQuery在1.5版本中首次引入了deferreds。它 所实现的方法与我们之前描述的抽象的概念没有大的差别。原则上,你获得了在未来某个时候得到‘延时’返回值的能力。在此之前是无法单独使用的。 Deferreds 作为对ajax模块较大重写的一部分添加进来,它遵循了CommonJS的promise/ A设计。1.5和先前的版本包含deferred功能,可以使$.ajax() 接收调用完成及请求出错的回调,但却存在严重的耦合。开发人员通常会使用其他库或工具包来处理延迟任务。新版本的jQuery提供了一些增强的方式来管理 回调,提供更加灵活的方式建立回调,而不用关心原始的回调是否已经触发。  同时值得注意的是,jQuery的递延对象支持多个回调绑定多个任务,任务本身可以既可以是同步也可以是异步的。

   您可以浏览下表中的递延功能,有助于了解哪些功能是你需要的:

 

jQuery.Deferred() 创建一个新的Deferred对象的构造函数,可以带一个可选的函数参数,它会在构造完成后被调用。
jQuery.when() 通过该方式来执行基于一个或多个表示异步任务的对象上的回调函数
jQuery.ajax() 执行异步Ajax请求,返回实现了promise接口的jqXHR对象
deferred.then(resolveCallback,rejectCallback) 添加处理程序被调用时,递延对象得到解决或者拒绝的回调。
deferred.done()

当延迟成功时调用一个函数或者数组函数.

deferred.fail()

当延迟失败时调用一个函数或者数组函数.。

deferred.resolve(ARG1,ARG2,...) 调用Deferred对象注册的‘done’回调函数并传递参数
deferred.resolveWith(context,args) 调用Deferred对象注册的‘done’回调函数并传递参数和设置回调上下文
deferred.isResolved 确定一个Deferred对象是否已经解决。
deferred.reject(arg1,arg2,...) 调用Deferred对象注册的‘fail’回调函数并传递参数
deferred.rejectWith(context,args) 调用Deferred对象注册的‘fail’回调函数并传递参数和设置回调上下文
deferred.promise() 返回promise对象,这是一个伪造的deferred对象:它基于deferred并且不能改变状态所以可以被安全的传递

 

  jQuery延时实现的核心是jQuery.Deferred:一个可以链式调用的构造函数。...... 需要注意的是任何deferred对象的默认状态是unresolved, 回调会通过 .then() 或 .fail()方法添加到队列,并在稍后的过程中被执行。  

  下面这个$.when() 接受多个参数的例子  

  1. function successFunc(){ console.log( “success!” ); }   
  2. function failureFunc(){ console.log( “failure!” ); }   
  3.   
  4. $.when(   
  5.    $.ajax( "/main.php" ),  
  6.    $.ajax( "/modules.php" ),  
  7.    $.ajax( “/lists.php” )  
  8.  ).then( successFunc, failureFunc );   

  在$.when() 的实现中有趣的是,它并非仅能解析deferred对象,还可以传递不是deferred对象的参数,在处理的时候会把它们当做deferred对象并立 即执行回调(doneCallbacks)。 这也是jQuery的Deferred实现中值得一提的地方,此外,deferred.then()还为deferred.done和 deferred.fail()方法在deferred的队列中增加回调提供支持。

  利用前面介绍的表中提到的deferred功能,我们来看一个代码示例。 在这里,我们创建一个非常基本的应用程序:通过$.get方法(返回一个promise)获取一条外部新闻源(1)并且(2)获取最新的一条回复。  同时程序还通过函数(prepareInterface())实现新闻和回复内容显示容器的动画。

  为了确保在执行其他相关行为前,上面的这三个步骤确保完成,我们使用$.when()。根据您的需要 .then()和.fail() 处理函数可以被用来执行其他程序逻辑。

  1. function getLatestNews() {   
  2.   return $.get( “latestNews.php”, function(data){   
  3.      console.log( “news data received” );   
  4.      $( “.news” ).html(data);   
  5.    } );  
  6.  }  
  7.  function getLatestReactions() {  
  8.     return $.get( “latestReactions.php”, function(data){   
  9.        console.log( “reactions data received” );  
  10.        $( “.reactions” ).html(data);  
  11.     } );  
  12.  }  
  13.   
  14. function prepareInterface() {   
  15.    return $.Deferred(function( dfd ) {   
  16.        var latest = $( “.news, .reactions” );  
  17.        latest.slideDown( 500, dfd.resolve );  
  18.        latest.addClass( “active” );  
  19.     }).promise();   
  20. }   
  21.   
  22. $.when(   
  23.     getLatestNews(), getLatestReactions(), prepareInterface()  
  24.  ).then(function(){   
  25.     console.log( “fire after requests succeed” );  
  26.  }).fail(function(){   
  27.     console.log( “something went wrong!” );  
  28.  });   

  deferreds在ajax的幕后操作中使用并不意味着它们无法在别处使用。 在本节中,我们将看到在一些解决方案中,使用deferreds将有助于抽象掉异步的行为,并解耦我们的代码。

异步缓存

  当涉及到异步任务,缓存可以是一个有点苛刻的,因为你必须确保对于同一个key任务仅执行一次。因此,代码需要以某种方式跟踪入站任务。 例如下面的代码片段:

  1. $.cachedGetScript( url, callback1 );   
  2. $.cachedGetScript( url, callback2 );  

  缓存机制需要确保 脚本不管是否已经存在于缓存,只能被请求一次。 因此,为了缓存系统可以正确地处理请求,我们最终需要写出一些逻辑来跟踪绑定到给定url上的回调。

  值得庆幸的是,这恰好是deferred所实现的那种逻辑,因此我们可以这样来做:

  1. var cachedScriptPromises = {};   
  2. $.cachedGetScript = function( url, callback ) {  
  3.      if ( !cachedScriptPromises[ url ] ) {   
  4.         cachedScriptPromises[ url ] = $.Deferred(function( defer ) {  
  5.             $.getScript( url ).then( defer.resolve, defer.reject );   
  6.         }).promise();   
  7.     }  
  8.     return cachedScriptPromises[ url ].done( callback );  
  9. };  

  代码相当简单:我们为每一个url缓存一个promise对象。 如果给定的url没有promise,我们创建一个deferred,并发出请求。 如果它已经存在我们只需要为它绑定回调。 该解决方案的一大优势是,它会透明地处理新的和缓存过的请求。 另一个优点是一个基于deferred的缓存 会优雅地处理失败情况。 当promise以‘rejected’状态结束的话,我们可以提供一个错误回调来测试:

  $.cachedGetScript( url ).then( successCallback, errorCallback );

  请记住:无论请求是否缓存过,上面的代码段都会正常运作!

通用异步缓存

  为了使代码尽可能的通用,我们建立一个缓存工厂并抽象出实际需要执行的任务​​:

  1. $.createCache = function( requestFunction ) {   
  2.     var cache = {};   
  3.     return function( key, callback ) {   
  4.         if ( !cache[ key ] ) {   
  5.             cache[ key ] = $.Deferred(function( defer ) {  
  6.                 requestFunction( defer, key );  
  7.              }).promise();   
  8.         }   
  9.         return cache[ key ].done( callback );  
  10.     };   
  11. }   

  现在具体的请求逻辑已经抽象出来,我们可以重新写cachedGetScript:

  1. $.cachedGetScript = $.createCache(function( defer, url ) {  
  2.       $.getScript( url ).then( defer.resolve, defer.reject );  
  3.  });  

  每次调用createCache将创建一个新的缓存库,并返回一个新的高速缓存检索函数。现在,我们拥有了一个通用的缓存工厂,它很容易实现涉及从缓存中取值的逻辑场景。

图片加载

  另一个候选场景是图像加载:确保我们不加载同一个图像两次,我们可能需要加载图像。 使用createCache很容易实现:

  1. $.loadImage = $.createCache(function( defer, url ) {   
  2.     var image = new Image();  
  3.      function cleanUp() {  
  4.         image.onload = image.onerror = null;   
  5.     }  
  6.      defer.then( cleanUp, cleanUp );  
  7.      image.onload = function() {   
  8.         defer.resolve( url );   
  9.     };   
  10.     image.onerror = defer.reject;   
  11.     image.src = url;  
  12.  });  

  接下来的代码片段如下:

  1. $.loadImage( "my-image.png" ).done( callback1 );   
  2. $.loadImage( "my-image.png" ).done( callback2 );   

  无论image.png是否已经被加载,或者正在加载过程中,缓存都会正常工作。

缓存数据的API响应

  哪些你的页面的生命周期过程中被认为是不可变的API请求,也是缓存完美的候选场景。 比如,执行以下操作:

  1. $.searchTwitter = $.createCache(function( defer, query ) {   
  2.     $.ajax({   
  3.         url: "http://search.twitter.com/search.json",   
  4.         data: { q: query },   
  5.         dataType: "jsonp",   
  6.         success: defer.resolve,   
  7.         error: defer.reject   
  8.     });   
  9. });  

  程序允许你在Twitter上进行搜索,同时缓存它们:

  1. $.searchTwitter( "jQuery Deferred", callback1 );   
  2. $.searchTwitter( "jQuery Deferred", callback2 );  
定时

  基于deferred的缓存并不限定于网络请求;它也可以被用于定时目的。

  例如,您可能需要在网页上给定一段时间后执行一个动作,来吸引用户对某个不容易引起注意的特定功能的关注或处理一个延时问题。 虽然setTimeout适合大多数用例,但在计时器出发后甚至理论上过期后就无法提供解决办法。 我们可以使用以下的缓存系统来处理:

  1. var readyTime;   
  2. $(function() { readyTime = jQuery.now(); });   
  3. $.afterDOMReady = $.createCache(function( defer, delay ) {   
  4.     delay = delay || 0;   
  5.     $(function() {  
  6.         var delta = $.now() - readyTime;   
  7.         if ( delta >= delay ) { defer.resolve(); }   
  8.         else {   
  9.             setTimeout( defer.resolve, delay - delta );  
  10.          }   
  11.     });   
  12. });  

  新的afterDOMReady辅助方法用最少的计数器提供了domReady后的适当时机。 如果延迟已经过期,回调会被马上执行。

同步多个动画

  动画是另一个常见的异步任务范例。 然而在几个不相关的动画完成后执行代码仍然有点挑战性。尽管在jQuery1.6中才提供了在动画元素上取得promise对象的功能,但它是很容易的手动实现:

  1. $.fn.animatePromise = function( prop, speed, easing, callback ) {   
  2.     var elements = this;   
  3.     return $.Deferred(function( defer ) {   
  4.         elements.animate( prop, speed, easing, function() {   
  5.         defer.resolve();   
  6.         if ( callback ) {   
  7.         callback.apply( this, arguments );  
  8.          }   
  9.         });   
  10.     }).promise();  
  11. };  

   然后,我们可以使用$.when()同步化不同的动画:

  1. var fadeDiv1Out = $( "#div1" ).animatePromise({ opacity: 0 }),   
  2.       fadeDiv2In = $( "#div1" ).animatePromise({ opacity: 1 }, "fast" );   
  3.   
  4. $.when( fadeDiv1Out, fadeDiv2In ).done(function() {   
  5.      /* both animations ended */  
  6.  });  

  我们也可以使用同样的技巧,建立了一些辅助方法:

  1. $.each([ "slideDown""slideUp""slideToggle""fadeIn""fadeOut""fadeToggle" ],   
  2. function( _, name ) {   
  3.     $.fn[ name + "Promise" ] = function( speed, easing, callback ) {  
  4.         var elements = this;   
  5.         return $.Deferred(function( defer ) {   
  6.             elements[ name ]( speed, easing, function() {   
  7.                 defer.resolve();   
  8.                 if ( callback ) {   
  9.                 callback.apply( this, arguments );   
  10.                 }   
  11.             });  
  12.          }).promise();   
  13.     };   
  14. });  

  然后想下面这样使用新的助手代码来同步动画:

  1. $.when(   
  2.     $( "#div1" ).fadeOutPromise(),   
  3.     $( "#div2" ).fadeInPromise( "fast" )   
  4. ).done(function() {  
  5.     /* both animations are done */  
  6.  });  
  7.    
一次性事件

  虽然jQuery提供你可能需要的所有的时间绑定方法,但当事件仅需要处理一次时,情况可能会变得有点棘手。( 与$.one() 不同 ) 

  例如,您可能希望有一个按钮,当它第一次被点击时打开一个面板,面板打开之后,执行特定的初始化逻辑。 在处理这种情况时,人们通常会这样写代码:

  1. var buttonClicked = false;   
  2. $( "#myButton" ).click(function() {   
  3.     if ( !buttonClicked ) {   
  4.         buttonClicked = true;   
  5.         initializeData();   
  6.         showPanel();   
  7.     }   
  8. });  

  不久后,你可能会在面板打开之后点击按钮时添加一些操作,如下:

  1. if ( buttonClicked ) { /* perform specific action */ }  

  这是一个非常耦合的解决办法。 如果你想添加一些其他的操作,你必须编辑绑定代码或拷贝一份。 如果你不这样做,你唯一的选择是测试buttonClicked。由于buttonClicked可能是false,新的代码可能永远不会被执行,因此你 可能会失去这个新的动作。

  使用deferreds我们可以做的更好 (为简化起见,下面的代码将只适用于一个单一的元素和一个单一的事件类型,但它可以很容易地扩展为多个事件类型的集合):

  1. $.fn.bindOnce = function( event, callback ) {   
  2.     var element = $( this[ 0 ] ),   
  3.     defer = element.data( "bind_once_defer_" + event );   
  4.     if ( !defer ) {   
  5.         defer = $.Deferred();   
  6.         function deferCallback() {   
  7.             element.unbind( event, deferCallback );   
  8.             defer.resolveWith( this, arguments );   
  9.         }   
  10.         element.bind( event, deferCallback )   
  11.         element.data( "bind_once_defer_" + event , defer );   
  12.     }   
  13.     return defer.done( callback ).promise();   
  14. };  

  该代码的工作原理如下:

  • 检查该元素是否已经绑定了一个给定事件的deferred对象
  • 如果没有,创建它,使它在触发该事件的第一时间解决
  • 然后在deferred上绑定给定的回调并返回promise

  代码虽然很冗长,但它会简化相关问题的处理。 让我们先定义一个辅助方法:

  1. $.fn.firstClick = function( callback ) {   
  2.        return this.bindOnce( "click", callback );  
  3.  };  

  然后,之前的逻辑可以重构如下:

  1. var openPanel = $( "#myButton" ).firstClick();   
  2. openPanel.done( initializeData );   
  3. openPanel.done( showPanel );  

  如果我们需要执行一些动作,只有当面板打开以后,所有我们需要的是这样的:

  1. openPanel.done(function() { /* perform specific action */ });     

  如果面板没有打开,行动将得到延迟到单击该按钮时。

组合助手

  单独看以上每个例子,promise的作用是有限的 。 然而,promise真正的力量是把它们混合在一起。

 

在第一次点击时加载面板内容并打开面板

  假如,我们有一个按钮,可以打开一个面板,请求其内容然后淡入内容。使用我们前面定义的助手方法,我们可以这样做:

  1. var panel = $( "#myPanel" );   
  2. panel.firstClick(function() {    
  3.     $.when(   
  4.         $.get( "panel.html" ),   
  5.         panel.slideDownPromise()  
  6.     ).done(function( ajaxResponse ) {   
  7.         panel.html( ajaxResponse[ 0 ] ).fadeIn();   
  8.     });  
  9. });  

在第一次点击时载入图像并打开面板

  假如,我们已经的面板有内容,但我们只希望当第一次单击按钮时加载图像并且当所有图像加载成功后淡入图像。HTML代码如下:

  1. <div id="myPanel">   
  2. <img data-src="image1.png" />  
  3.  <img data-src="image2.png" />  
  4.  <img data-src="image3.png" />   
  5. <img data-src="image4.png" />   
  6. </div>  

  我们使用data-src属性描述图片的真实路径。 那么使用promise助手来解决该用例的代码如下:

  1. $( "#myButton" ).firstClick(function() {   
  2.     var panel = $( "#myPanel" ),   
  3.     promises = [];   
  4.     $( "img", panel ).each(function() {   
  5.         var image = $( this ), src = element.attr( "data-src" );   
  6.         if ( src ) {   
  7.         promises.push(   
  8.             $.loadImage( src ).then( function() {  
  9.                     image.attr( "src", src );   
  10.                 }, function() {   
  11.                     image.attr( "src""error.png" );  
  12.             } )   
  13.         );  
  14.         }   
  15.     });   
  16.   
  17.     promises.push( panel.slideDownPromise() );   
  18.   
  19.     $.when.apply( null, promises ).done(function() { panel.fadeIn(); });   
  20. });  

  这里的窍门是跟踪所有的LoadImage 的promise,接下来加入面板slideDown动画。 因此首次点击按钮时,面板将slideDown并且图像将开始加载。 一旦完成向下滑动面板和已加载的所有图像,面板才会淡入。

在特定延时后加载页面上的图像

  假如,我们要在整个页面实现递延图像显示。 要做到这一点,我们需要的HTML的格式如下:

  1. <img data-src="image1.png" data-after="1000" src="placeholder.png" />   
  2. <img data-src="image2.png" data-after="1000" src="placeholder.png" />   
  3. <img data-src="image1.png" src="placeholder.png" />   
  4. <img data-src="image2.png" data-after="2000" src="placeholder.png" />  

  意思非常简单:

  • image1.png,第三个图像立即显示,一秒后第一个图像显示
  • image2.png 一秒钟后显示第二个图像,两秒钟后显示第四个图像

  我们将如何实现呢?

  1. $( "img" ).each(function() {  
  2.     var element = $( this ),  
  3.         src = element.attr( "data-src" ),  
  4.         after = element.attr( "data-after" );  
  5.     if ( src ) {  
  6.         $.when(  
  7.             $.loadImage( src ),  
  8.             $.afterDOMReady( after )  
  9.         ).then(function() {  
  10.             element.attr( "src", src );  
  11.         }, function() {  
  12.             element.attr( "src""error.png" );  
  13.         } ).done(function() {  
  14.             element.fadeIn();  
  15.         });  
  16.     }  
  17. });  

   如果我们想延迟加载的图像本身,代码会有所不同:

  1. $( "img" ).each(function() {  
  2.     var element = $( this ),  
  3.         src = element.attr( "data-src" ),  
  4.         after = element.attr( "data-after" );  
  5.     if ( src ) {  
  6.         $.afterDOMReady( after, function() {  
  7.             $.loadImage( src ).then(function() {  
  8.                 element.attr( "src", src );  
  9.             }, function() {  
  10.                 element.attr( "src""error.png" );  
  11.             } ).done(function() {  
  12.                 element.fadeIn();  
  13.             });  
  14.         } );  
  15.     }  
  16. });  

  这里,我们首先在尝试加载图片之前等待延迟条件满足。当你想在页面加载时限制网络请求的数量会非常有意义。

结论

  正如你看到的,即使在没有Ajax请求的情况下,promise也非常有用的。通过使用jQuery 1.5中的deferred实现 ,会非常容易的从你的代码中分离出异步任务。 这样的话,你可以很容易的从你的应用程序中分离逻辑。

您可能感兴趣的相关文章

 

本文链接:使用 jQuery Deferred 和 Promise 创建响应式应用

译者:点点滴滴博客,原文:Creating Responsive Applications Using jQuery Deferred and Promises

作者:山边小溪 
出处:jizhula.com 记住啦:) 
欢迎任何形式的转载,但请务必注明出处。



  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
山东省基本农田shp文件是指将山东省内的基本农田信息以shp文件的形式进行存储和传输的文件。基本农田是指国家规划确定的农业用地保护的核心区域,具有重要的农业生产和生态功能。 这个shp文件包含了关于山东省基本农田的地理空间数据,如基本农田的边界、面积、位置等信息。它采用矢量数据格式,可以使用GIS软件进行打开和处理。 有了这个shp文件,可以进行一系列的农田规划和管理工作。首先,可以利用shp文件的空间属性,对基本农田进行空间分析和统计,统计山东省基本农田的总面积、分布情况等。根据这些数据,可以制定农田保护政策,合理规划农业用地,保护耕地资源。 其次,基于该shp文件,可以进行土地评价和农田质量分析,了解基本农田的土壤质量、水资源状况、适宜农作物类型等信息,以指导农业生产和土地管理。还可以结合其他数据,如气候数据和经济数据等,进行农田利用和农产品供给的研究与决策。 此外,该shp文件还可以与其它地理信息数据进行叠加分析,如交通网络数据、地形数据等,从而评估基本农田的承载能力、利用潜力及其与其他领域的关联性。 总之,山东省基本农田shp文件是一份具有重要参考价值的数据文件,对于山东省农田资源的保护、合理利用以及农业生产的规划与管理都起到了重要作用。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值