zoukankan      html  css  js  c++  java
  • Deferred在jQuery和Angular中的使用与简单实现

    Deferred在jQuery和Angular中的使用与简单实现

    Deferred是在jQuery1.5版本中加入的,并且jQuery使用它完全重写了AJax,以前也只是偶尔使用.但是上次在使用Angular做一个小应用的时候,遇到一个问题,

    我将我的AJax请求放在了自己定义的factory中,并在factory编写回调函数,返回处理后的对象,然后将这个factory注入到controller中,然后我在controller中就开始对这个对象进行操作,进行一些和View上的数据绑定.

    当我这样使用的时候,总是出现了问题.我在controller首次执行的时候,总是拿不到数据,而导致我的界面无法渲染,必须在一定的延迟后再次从服务中获取对象,而由于网络的不稳定性,我不知道需要设置延迟为多少.

    这让我纠结很久.

    我尝试使用同步aJax,但是Angular中没有提供相应的实现.

    想了很久,使用了下面的代码:

    factory:

    ngApp.factory('UserInfoFactory', ['$http', '$q', function ($http, $q) {  
      var json = null;
      var http = $http({method: 'GET', url: 'XXXX.ashx '});
      return {
        call:function(fn){
          http.success(function(data){
            fn(data);
          })
        }
      }
    

    controller

    ngApp.controller('MainController', ['$scope', 'UserInfoFactory', function ($scope, UserInfoFactory) { // 引用我们定义的UserInfo服务  
       var func = function(data){
         ...
       }
       UserInfoFactory().call(func);
        
      }]); 
    

    以上的代码可以办到,就在整合代码时,想到了一种更好的方式:

    deferred

    Angular中的deferred服务通过注入$q来实现,下面我们看一个简单的Demo,用来指出我正在干什么.

    这是factory的代码

    // $q 是内置服务,所以可以直接使用  
    ngApp.factory('UserInfoFactory', ['$http', '$q', function ($http, $q) {  
      return {  
        query : function() {  
          var deferred = $q.defer(); //申明deferred对象 
          $http({method: 'GET', url: 'XXXX.ashx '}).  
          success(function(data) {  
            deferred.resolve(data);  // 声明执行成功,即http请求数据成功,可以返回数据了  
          }).  
          return deferred.promise;   // 返回承诺,这里并不是最终数据,而是访问最终数据的API  
        } 
      };  
    }]); 
    

    其实我不是很懂为什么要用$q来命名,$defer不是更好嘛..

    这是controller中的代码

    
    ngApp.controller('MainController', ['$scope', 'UserInfoFactory', function ($scope, UserInfoFactory) { // 引用我们定义的UserInfo服务  
        var promise = UserInfoFactory.query(); // 同步调用,获得承诺接口  
        promise.then(function(data) {  // 调用承诺API获取数据 .resolve  
            $scope.user = data;  
        });  
      }]); 
    

    我在controller里面使用Query拿到了promise对象,然后在then里面执行回调,在这里我已经可以确保我拿到正确调用的回调函数了,
    这简直棒极了.


    jQuery 中的deferred介绍

    因为上面的原因,我打算重新把deferred理一遍,然后自己实现一个简单的deferred.
    首先我们来看jQuery中的deferred对象.

    使用过jQuery中的deferred的肯定了解,deferred中的三个函数

    1. 调用三个触发函数resolve,reject,notify,分别会触发done,fail,progres回调函数.
    2. 三个回调函数, 在里面编写自己的逻辑代码.
    3. 返回promise对象,只包含三个回调函数.

    下面查看一些具体的实例,来了解如何使用deferred.

    resolve/done

    function cb() {
        alert('success')
    }
    var deferred = $.Deferred()
    deferred.done(cb); //在这里会看到3s后才弹出
    setTimeout(function() {
        deferred.resolve()
    }, 3000)
    

    reject/fail

    function cb() {
        alert('fail')
    }
    var deferred = $.Deferred()
    deferred.fail(cb); // 在这里,3s后弹出fail,表示失败 
    setTimeout(function() {
        deferred.reject()
    }, 3000)
    

    notify/progress

    function cb(num) {
        alert(num)
    }
    var deferred = $.Deferred()
    deferred.progress(cb);// 会不断地弹出数字并递增
    var num = 0;
    setInterval(function() { 
        num++;
        deferred.notify(num)   
    }, 2000)
    

    由于notify的这个好处,用来做任务进度非常合适.

    我们称 resolve,reject,notify 这3个函数为改变状态的函数,当状态改变,触发回调函数.

    由于他们3个函数在deferred对象中,因此可以随意地改变状态以触发回调函数,这太不好了.

    因此,deferred还提供了一个对象,promise,它只包含响应的回调函数,而不能改变状态,这常常是很多异步操作暴露出来的接口,比如,jQuery实现的aJax,在封装的过程中改变状态,而仅仅暴露了promise对象.

    这也就是promise a+ 规范,是属于CommonJs范畴的,感兴趣的可以多了解.

    看下面的函数调用

    function getPromise(){
        return $.Deferred().promise();
    }
    try{
        getPromise().resolve("a");
    } catch(err) {
        console.log(err);
    }
    

    上述的调用就会抛出异常,promise对象是不能改变状态的.

    在deferred和promise中,还有很多其他的函数和属性,then,when,always,state,等待,而且when和then的实现相当复杂和巧妙,但是在这里就不再概述.

    下面我们开始实现deferred,


    实现deferred

    我通过构造函数的方式来创建deferred对象

    var deferred = function () {
        this._init_();
    }
    deferred.prototype = {
        constructor: deferred,
        _init_: function () {
            this.resolve_list = [];
            this.reject_list = [];
            this.notify_list = [];
        },
    }
    

    这是面向对象封装的一种比较好的方式,也是很多大牛推荐使用的.

    恩 比较绕的地方就是new的执行原理,我在JavaScript面向对象高级会介绍.

    好了,下面开始主要的逻辑代码

    为了避免代码比较绕,我没有使用数组,而是一个一个写出来,这样代码会显得很冗余

    deferred.prototype = {
        constructor: deferred,
        _init_: function () {
            this.resolve_list = [];
            this.reject_list = [];
            this.notify_list = [];
        },
     
        resolve: function () {
            for (var i = 0; i < this.resolve_list.length; i++) {
                this.resolve_list[i].apply(this, arguments);
            }
           // 在jQuery中,使用了函数管理 $.callbacks('once')的方式,确保resolve只能被调用一次,
           //  为了避免引入复杂的逻辑,我就直接把数组清空,这样,resolve也就只能执行一次.
            this.resolve_list[i] = [];
           // return this 是为了链式编程,我这里返回的还是deferred对象. 
            return this;
        },
        done: function (func) {
    		// 由于 func 可以是多个函数的数组,所以在这里进行一个判断,如果不是数组,那么就变成数组.
            if (typeof func === 'function') {
                func = [func];
            }
            this.resolve_list.push.apply(this.resolve_list, func);
            return this;
        },
        reject: function () {
            for (var i = 0; i < this.reject_list.length; i++) {
              // arguments 参数表示 调用reject传入的参数,可以通过arguments的方式以一个数组的方式获取
              // 所以下面的调用也要使用apply.
                this.reject_list[i].apply(this, arguments);
            }
            this.reject_list = [];
            return this;
    
        },
        fail: function (func) {
            if (typeof func === 'function') {
                func = [func];
            }
            this.reject_list.push.apply(this.reject_list, func);
            return this;
        },
        notify: function () {
            for (var i = 0; i < this.notify_list.length; i++) {
                this.notify_list[i].apply(this, arguments);
            }
            return this;
        },
        progress: function (func) {
            if (typeof func === 'function') {
                func = [func];
            }
            this.notify_list.push.apply(this.notify_list, func);
            return this;
        }
    }
    

    好了,基本架子已经完成,下面我们测试一下.

    var d1 = new deferred();
    // 为函数列表添加事件,链式编程.
    d1.done(function (arg) {
        console.log(arg);
    }).fail(function (arg) {
        console.log(arg);
    }).progress(function (arg) {
        console.log(arg);
    })
    console.log(new Date().toLocaleTimeString() + '      first init');
    setTimeout(function () {
        d1.resolve(new Date().toLocaleTimeString() + '    fire resolve');
    }, 2000);
    
    
    setTimeout(function () {
        d1.reject(new Date().toLocaleTimeString() + '    first reject');
    }, 3000);
    
    var num = 0;
    setInterval(function () {
        d1.notify(new Date().toLocaleTimeString() + '    first notify')
    }, 1000);
    

    下面是结果:

    是的,成功了!

    jQuery中的deferred相当复杂和巧妙,实际上我也还没有完全理解它的代码,写出这样代码的人真是伟大!

    好了,最简单的deferred已经实现了,但是这样似乎太简单了点,下面,我要为她实现一个promise


    promise

    其实实现promise很简单,只需要将deferred上的done,fail和progress方法绑定到promise方法的返回值上就行了.

    ,需要注意的是,由于这些函数中的this值的是原来的deferred对象,但是将这些方法绑定到promise返回值的时候,this就不再是deferred对象了,所以我们需要重新制定这些函数列表

        promise: function () {
    
            var that = this;
            return {
                resolve_list: that.resolve_list,
                reject_list: that.reject_list,
                notify_list: that.notify_list,
                done: that.done,
                fail: that.fail,
                progress: that.progress
            }
    
        }
    

    下面测试

    var d2 = new deferred();
    d2.promise()
        .done(function (data) {
            console.log(new Date().toLocaleTimeString() + data);
        })
        .fail(function (data) {
            console.log(new Date().toLocaleTimeString() + data);
        })
        .progress(function (data) {
            console.log(new Date().toLocaleTimeString() + data);
        });
    
    console.log(new Date().toLocaleTimeString() + '    ' + 'first init');
    setTimeout(function () {
        d2.resolve('     first resolve');
    }, 3000);
    setTimeout(function () {
        d2.reject('     first reject');
    }, 2000);
    
    
    setInterval(function () {
        d2.notify('    first notify');
    }, 1000);
    

    测试结果

    好了,这个简单版的deferred,它实在是太简化了.

    Deferred在jQuery和Angular中的使用与简单实现(二) 中 我会添加when 方法和then方法以及其他jQuery中的大部分方法. 最后会整合到JavaScript框架设计模块中,

    代码 我会托管到github中.

  • 相关阅读:
    信息收集
    beef框架使用
    网站程序CMS识别
    查找网站后台
    下载漏洞原理及利用
    后台拿webshell方法(2)
    如何学好web安全
    TIME_WAIT过多的解决方法(转)
    sudo
    keepalived+lvs子网掩码造成VIP切换故障 + vrrp_script+track_script
  • 原文地址:https://www.cnblogs.com/likeFlyingFish/p/5769339.html
Copyright © 2011-2022 走看看