概述
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.$$resolve和this.$$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···