• 关于Promise的一些个人理解jQuery的deferred


     


    一、什么是deferred对象?

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

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

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

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

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

    来看下面的一段代码

    function step1(callback) {
        $.ajax({
            url : '/api/test',
            type : 'POST',
            data : {
                ...
            },
            success : function (res) {
                callback && callback();
            }
        })
    }

    当我们使用回调来解决实际中的问题时,很容易不知不觉中出现代码金字塔,嵌套层次就会越深,代码可读性就会越差。

    step1(function () {
        step2(function () {
            step3(function () {
                step4(function () {
                    step5();
                })
            })
        })
    })

    ES6 原生提供了 Promise 对象。

    所谓 Promise,就是一个对象,用来传递异步操作的消息。它代表了某个未来才会知道结果的事件(通常是一个异步操作),并且这个事件提供统一的 API,可供进一步处理。

    Promise 对象有以下两个特点。

    (1)对象的状态不受外界影响。Promise 对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称 Fulfilled)和 Rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是 Promise 这个名字的由来,它的英语意思就是「承诺」,表示其他手段无法改变。

    (2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise 对象的状态改变,只有两种可能:从 Pending 变为 Resolved 和从 Pending 变为 Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

    有了 Promise 对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise 对象提供统一的接口,使得控制异步操作更加容易。

    Promise 也有一些缺点。首先,无法取消 Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。第三,当处于 Pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

    基本的 api

    1. Promise.resolve()

    2. Promise.reject()

    3. Promise.prototype.then()

    4. Promise.prototype.catch()

    5. Promise.all() // 所有的完成

    所谓“承诺”

    Promise这个单词的意思是“承诺”,就是我们日常说的“我肯定帮你买早饭”、“你如果交了钱我肯定给你一杯咖啡”。

    在程序世界,举例可以说:“我承诺给你完成这些代码执行”。new一个Promise实例,就是JS引擎对你做了一个承诺。

    既然是承诺,就肯定有成功的时候有失败的时候,比如我帮你买早餐,结果今天煎饼果子没出摊,或者是我走到半路上,煎饼果子的塑料袋破裂,煎饼果子滑落到了地上,这就是失败。就连“我们承诺绝不首先动用核武器”都有坚持不下去的时候,所以只要是承诺就有成功和失败,只不过是概率问题。

    到程序世界,一个承诺也会有三种状态,就是“未决的”、“成功的”、“失败的”三种状态。也就是pendding/resolved/rejected三种状态。


    Promise构造函数的超能力

    Promises写法的本质就是把异步写法撸成同步写法。要做这么酷炫这么变态的事情,当然需要Promise构造函数有超能力,它的超能力就是传入Promise构造函数的函数参数会第一优先执行,无论这个函数多么的繁复,有多少层回调,有多少秒的计数器,统统都会最优先执行,也就是说,我们只要new了一个Promise(),那么Promise构造函数的函数参数就是最高优先级执行,一直到new出一个promise对象实例,后面的.then()代码才会执行。链条上的每一个.then都会等前面的promise有了结果才会执行,Promise构造函数的这个超能力是Promises系统的威力之源。(当然,这里说的执行优先级,是在理想环境下,所谓理想环境也就是全部执行代码只由new Promise()和它的一系列.then()方法组成。如果方法链之外还有其他代码,那么整体代码执行的先后顺序就复杂化了,下文有介绍。然而,Promise加它的then方法链已经提供了梳理代码执行顺序的整套方案,如果在方法链之外还写代码的话属于画蛇添足、自找麻烦,是不科学的写法,应该避免这么做。)

    ajax操作的链式写法

     $.ajax("test.html")
    
      .done(function(){ alert("哈哈,成功了!"); })
    
      .fail(function(){ alert("出错啦!"); });

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

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

    var wait = function(){
    
        var tasks = function(){
    
          alert("执行完毕!");
    
        };
    
        setTimeout(tasks,5000);
    
      };

    但是,这样写的话,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("出错啦!"); });

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

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

    要说清楚这个问题,就要引入一个新概念"执行状态"。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("出错啦!"); });

    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();

    我在代码的尾部加了一行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(); // 此时,这个语句是无效的

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

    不过,更好的写法是将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("出错啦!"); });

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

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

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

     $.Deferred(wait)
    
      .done(function(){ alert("哈哈,成功了!"); })
    
      .fail(function(){ alert("出错啦!"); });

    除了上面两种方法以外,我们还可以直接在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);

    这里的关键是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()。

    deferred.always()

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

     $.ajax( "test.html" )
    
      .always( function() { alert("已执行!");} );

    本文转载:http://www.ruanyifeng.com/blog/2011/08/a_detailed_explanation_of_jquery_deferred_object.html

    如果这篇文章对您有帮助,您可以打赏我

    技术交流QQ群:15129679

  • 相关阅读:
    ios qq 分享 失败
    Collections在sort()简单分析法源
    C# char[]与string之间的相互转换
    uva 10837
    良好的互联网站点
    SVN库迁移
    Android 它们的定义View它BounceProgressBar
    #AOS应用基础平台# 添加了用户自己定义快捷菜单在平铺布局下的用户自己定义排序管理
    android 逆向project smail 语法学习
    Linux内核-系统调用
  • 原文地址:https://www.cnblogs.com/yeminglong/p/9504814.html
Copyright © 2020-2023  润新知