概述

Augular中的$q服务提供了Promise的实现,这个服务叫做“q”是因为它是Kris Kowal’s Q的一种植入。

从Augular中$q服务的文档中可以了解到,这个服务有两种用法,一种在某种程度上和ES6的Promise规范相似,而另一种则和Q以及jQuery的Deferred接口类似。

因此在Angular中$q的具体代码实现有两种,一种是将$q作为一个构造函数,并且接收一个resolver()函数作为唯一参数。这和ES6的原生实现相似:

// for the purpose of this example let's assume that variables `$q` and `okToGreet`
// are available in the current lexical scope (they could have been injected or passed in).

function asyncGreet(name) {
  // perform some asynchronous operation, resolve or reject the promise when appropriate.
  return $q(function(resolve, reject) {
    setTimeout(function() {
      if (okToGreet(name)) {
        resolve('Hello, ' + name + '!');
      } else {
        reject('Greeting ' + name + ' is not allowed.');
      }
    }, 1000);
  });
}

var promise = asyncGreet('Robin Hood');
promise.then(function(greeting) {
  alert('Success: ' + greeting);
}, function(reason) {
  alert('Failed: ' + reason);
});

而另一种实现则是更为传统的CommonJS风格的实现,将Promise描述成一个接口,来和一个代表了未来结果的异步请求发生交互。

代码如下:

// for the purpose of this example let's assume that variables `$q` and `okToGreet`
// are available in the current lexical scope (they could have been injected or passed in).

function asyncGreet(name) {
  var deferred = $q.defer();

  setTimeout(function() {
    deferred.notify('About to greet ' + name + '.');

    if (okToGreet(name)) {
      deferred.resolve('Hello, ' + name + '!');
    } else {
      deferred.reject('Greeting ' + name + ' is not allowed.');
    }
  }, 1000);

  return deferred.promise;
}

var promise = asyncGreet('Robin Hood');
promise.then(function(greeting) {
  alert('Success: ' + greeting);
}, function(reason) {
  alert('Failed: ' + reason);
}, function(update) {
  alert('Got notification: ' + update);
});

##源码分析:deferred API 因为$q服务中的ES6形式实现实际上是在deferred api的基础上的一种封装实现,所以我们先来看看deferred API的源代码。
这部分代码的结构看起来是这样的:

//deferred method: return a instance of Deferred object
var deferred = function() {
    return new Deferred();
};

//construct function of Promise object
function Promise() {
    this.$$state = { status: 0 };
}

//then, catch, and finally method of Promise object
extend(Promise.prototype, {
    then: function(onFulfilled, onRejected, progressBack) {
      //...
    },

    "catch": function(callback) {
      //...
    },

    "finally": function(callback, progressBack) {
      //...
    }
});


//construct function of Deferred object, the promise object is a method 
//of Deferred object. And resolve, reject, notify apis are binded here.
function Deferred() {
    this.promise = new Promise();
    //Necessary to support unbound execution :/
    this.resolve = simpleBind(this, this.resolve);
    this.reject = simpleBind(this, this.reject);
    this.notify = simpleBind(this, this.notify);
}



extend(Deferred.prototype, {
    resolve: function(val) {
    	//...
    },

    $$resolve: function(val) {
    	//...
    },

    reject: function(reason) {
    	//...
    },

    $$reject: function(reason) {
    	//...
    },

    notify: function(progress) {
   		//...
    }
  });

###具体分析:Deferred.promise.then()

then: function(onFulfilled, onRejected, progressBack) {
	   //check if at least one para was passed in, otherwise return.
      if (isUndefined(onFulfilled) && isUndefined(onRejected) && isUndefined(progressBack)) {
        return this;
      }
      //instantiate a new Deferred object as result
      var result = new Deferred();
      
	   //instantiate $$state.pending
      this.$$state.pending = this.$$state.pending || [];
      //push paras of this into $$state.pending array
      this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]);
      //if $$state.status changed, add this.$$state to the process 
      queue
      if (this.$$state.status > 0) scheduleProcessQueue(this.$$state);
		
	  //return the new Deferred object instance's promise object
      return result.promise;
}

then()方法接收三个参数,promise条件满足时执行的函数onFulfilled,被拒绝时的回调函数onRejected,以及一个通知用回调函数progressBack
这里的逻辑是then()方法接收参数之后,实例化一个新Deferred对象,并将参数推入pending数组中。 关于then,有趣的一点在于then的链式调用的实现,在分析这点之前,我们把目光移到这个api的大局上来,看看promise的状态变化以及状态转移是如何实现的。

###具体分析:Deferred.resolve()

在promise对象内部,收到请求后具体执行状态转移的便是resolve()reject()函数了,我们还可以向其中传递一个唯一的参数,来作为resolve()的value或是reject()的reason。因为这两个方法原理大致类似(在源码中这两个方法的相关代码有较大的不同,应该和这个两种行为不同的结果有关),所以我们就以resolve()方法为例来说明。

resolve: function(val) {
		//if $$state not equals 0, which means the state had already 
		changed, return.
      if (this.promise.$$state.status) return;
      //reject if val is this.promise its self.
      if (val === this.promise) {
        this.$$reject($qMinErr(
          'qcycle',
          "Expected promise to be resolved with value other than itself '{0}'",
          val));
      } else {
      //resolve val with inner method $$resolve
        this.$$resolve(val);
      }

    },

$$resolve: function(val) {
      var then, fns;
	  //warp the functions in order to be called only once
      fns = callOnce(this, this.$$resolve, this.$$reject);
      try {
        if ((isObject(val) || isFunction(val))) then = val && val.then;
        if (isFunction(then)) {
          this.promise.$$state.status = -1;
          then.call(val, fns[0], fns[1], this.notify);
        } else {
          this.promise.$$state.value = val;
          this.promise.$$state.status = 1;
          scheduleProcessQueue(this.promise.$$state);
        }
      } catch (e) {
        fns[1](e);
        exceptionHandler(e);
      }
}

resolve()方法接收一个值val,这个值可能是对象/函数,也有可能是普通的数值或是字符串。resolve()方法首先判断Deferred.promise.$$state.status的值是否不为0,如果不为0则说明状态已经转移了,那么就不再继续执行。如果一切正常,在执行resolve()方法时,Deferred.promise.$$state.status的值为初始的0,然后val被传入一个内部函数$$resolve(),来进行真正的处理。$$resolve()函数首先调用callOnce()函数来确保自己只被调用一次,然后判断val的数据类型,如果是对象或者是函数,则将val赋值给then,其中then = val && val.then;这个语句用来将resolve()中可能传入的promise对象的then()方法赋值给then。下一步,如果then是函数,也就是说传入的val是一个promise对象的话,则将Deferred.promise.$$state.status的值设为-1,之后then.call(val, fns[0], fns[1], this.notify);这是将fns[0], fns[1], this.notify三个函数作为参数传入了这个promise对象的then方法中,其中前两者便是经过了callOnce()函数处理的this.$$resolvethis.$$reject函数,这一步设置了被传入的promise对象的pending list。
如果val不是函数,那么接下去的逻辑便很容易理解了,this.promise.$$state.value被赋值为val的值,this.promise.$$state.status的值被设置为1,1这个状态码便代表了promise对象目前处于fulfilled状态。
接下去调用scheduleProcessQueue()函数,并传入this.promise.$$state对象。这个函数实际是调用了ProcessQueue()函数,不过加入了一些angular内部的检查机制,来保证函数调用和angular内部运行的同步。而ProcessQueue()函数则真正执行then()方法推入到pending list中的回调函数。

下面我们简单的来看看ProcessQueue函数:

function processQueue(state) {
    var fn, deferred, pending;

    pending = state.pending;
    state.processScheduled = false;
    state.pending = undefined;
    for (var i = 0, ii = pending.length; i < ii; ++i) {
      deferred = pending[i][0];
      fn = pending[i][state.status];
      try {
        if (isFunction(fn)) {
          deferred.resolve(fn(state.value));
        } else if (state.status === 1) {
          deferred.resolve(state.value);
        } else {
          deferred.reject(state.value);
        }
      } catch (e) {
        deferred.reject(e);
        exceptionHandler(e);
      }
    }
  }

processQueue()函数简单地将当前promise对象的pending list按传入的状态码进行处理。fn = pending[i][state.status];中,status为1,那么就调用pending list的第二项,也就是onFulfilled情况下的回调函数。之后 deferred.resolve(fn(state.value));中,实际上执行了一次eval(fn(state.value)),回调函数就是在这里被执行的,之后deferred.resolve(val)便将回调函数处理之后的值传给了新创建的deferred对象。

###实例分析:Deferred.promise.then()的链式调用机制

          var Deferred = $q.defer();
          var promise1 = Deferred.promise;
          var promise2 = promise1.then(function (data) {
				 return data  + 1;
          });
          promise2.then(function (val) {
               console.log(val);
          }); 
          Deferred.resolve(10);

这段代码的预期结果是promise2的回调函数会输出11。让我们来看看这是怎样实现的。

首先,promise1的then()方法被执行,回调函数被推入pending list中,一个新的deferred对象被创建,deferred.promise对象被返回,我们将这个新创建的promise对象赋值给promise2。同样的,promise2的then()方法也被执行。所以我们把promise1的pending list称谓list1,promise2的pending list称为list2。

之后,Deferred.resolve(10);被执行,在这里这是同步的,但一般来说这是异步执行的。此时执行resolve()方法,后数据被传入内部的$$resolve()方法,因为val是数值,因此$$state对象的value被设置为10,而状态码也被设置为相应的1,代表解析成功。

之后的任务交给了processQueue()函数,因为list1中的第二个参数是我们通过then()方法传入的回调函数,这个函数执行deferred.resolve(fn(state.value));,回调函数执行,并返回值11,这里的deferred对象便是promise1的then()方法创建的新deferred对象,也就是list1中的deferred对象,是promise2的宿主对象。

所以deferred.resolve(fn(state.value));的执行,执行了又一次resolve()函数,只是这次的上下文换成了promise2所在的上下文,那么接下去的流程便和promise1发生的一致了。最后list2中的回调执行,输出值11。

让我们总结一下,then()方法实际上是负责在运行时挂载回调函数列表,而resolve()函数则负责异步触发函数执行。链式调用是通过返回新promise对象,并配合then()方法的同步加载回调以便获取新上下文来实现的。

tbc···


参考-A look at Angular’s promise implementation