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