Deferred在jQuery和Angular中的使用與簡單實現

Deferred在jQuery和Angular中的使用與簡單實現

Deferred是在jQuery1.5版本中加入的,而且jQuery使用它徹底重寫了AJax,之前也只是偶爾使用.可是上次在使用Angular作一個小應用的時候,遇到一個問題,javascript

我將個人AJax請求放在了本身定義的factory中,並在factory編寫回調函數,返回處理後的對象,而後將這個factory注入到controller中,而後我在controller中就開始對這個對象進行操做,進行一些和View上的數據綁定.java

當我這樣使用的時候,老是出現了問題.我在controller首次執行的時候,老是拿不到數據,而致使個人界面沒法渲染,必須在必定的延遲後再次從服務中獲取對象,而因爲網絡的不穩定性,我不知道須要設置延遲爲多少.jquery

這讓我糾結好久.git

我嘗試使用同步aJax,可是Angular中沒有提供相應的實現.github

想了好久,使用了下面的代碼:編程

factory:json

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);
    
  }]);

以上的代碼能夠辦到,就在整合代碼時,想到了一種更好的方式:promise

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中.

相關文章
相關標籤/搜索