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裏面執行回調,在這裏我已經能夠確保我拿到正確調用的回調函數了,
這簡直棒極了.
***
由於上面的緣由,我打算從新把deferred理一遍,而後本身實現一個簡單的deferred.
首先咱們來看jQuery中的deferred對象.
使用過jQuery中的deferred的確定了解,deferred中的三個函數
下面查看一些具體的實例,來了解如何使用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對象
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很簡單,只須要將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中.