Promise 具有三种状态
- 待定- 这就是承诺的开始方式。
- Fulfilled - 当你解决一个 deferred 或者从
.then
fulfill 的返回值时会发生这种情况,它通常类似于标准返回值。
- Rejected - 当你拒绝一个延迟的,当你
throw
从一个.then
处理程序中,或者当你返回一个解包为拒绝的承诺时会发生这种情况*,它通常类似于抛出的标准异常。
$rootScope.$evalAsync(callback);
在 Angular 中,promise 是异步解析的,并通过解析 via (取自此处)来提供保证。
由于它是通过运行的,$evalAsync
我们知道在 Promise 解决后(通常)至少会发生一个摘要循环,因为如果一个新摘要不在进行中,它将安排一个新的摘要。
这也是为什么例如当你想在 Angular 中对 Promise 代码进行单元测试时,你需要运行一个摘要循环(通常是在rootScope
via 上$rootScope.digest()
),因为 $evalAsync 执行是摘要循环的一部分。
好的,说得够多了,给我看代码:
注意:这显示了来自 Angular 1.2 的代码路径,Angular 1.x 中的代码路径都相似,但在 1.3+ 中,$q 已被重构为使用原型继承,因此这个答案在代码中并不准确(但在精神上)那些版本。
1) 创建 $q 时,它会执行以下操作:
this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) {
return qFactory(function(callback) {
$rootScope.$evalAsync(callback);
}, $exceptionHandler);
}];
反过来,它会:
function qFactory(nextTick, exceptionHandler) {
并且仅解析为内部解析并通知nextTick
传递:$evalAsync
resolve: function(val) {
if (pending) {
var callbacks = pending;
pending = undefined;
value = ref(val);
if (callbacks.length) {
nextTick(function() {
var callback;
for (var i = 0, ii = callbacks.length; i < ii; i++) {
callback = callbacks[i];
value.then(callback[0], callback[1], callback[2]);
}
});
}
}
},
在根范围内,$evalAsync 定义为:
$evalAsync: function(expr) {
// if we are outside of an $digest loop and this is the first time we are scheduling async
// task also schedule async auto-flush
if (!$rootScope.$$phase && !$rootScope.$$asyncQueue.length) {
$browser.defer(function() {
if ($rootScope.$$asyncQueue.length) {
$rootScope.$digest();
}
});
}
this.$$asyncQueue.push({scope: this, expression: expr});
},
$$postDigest : function(fn) {
this.$$postDigestQueue.push(fn);
},
如您所见,如果我们不在其中并且之前没有安排摘要,则确实会安排摘要。然后它将函数推送到$$asyncQueue
.
依次在 $digest 内部(在一个循环中,在测试观察者之前):
asyncQueue = this.$$asyncQueue,
...
while(asyncQueue.length) {
try {
asyncTask = asyncQueue.shift();
asyncTask.scope.$eval(asyncTask.expression);
} catch (e) {
clearPhase();
$exceptionHandler(e);
}
lastDirtyWatch = null;
}
所以,正如我们所见,它一直在运行,$$asyncQueue
直到它为空,执行你的 Promise 中的代码。
因此,正如我们所见,更新作用域只是简单地分配给它,如果摘要尚未运行,则将运行摘要,如果是,则在运行$evalAsync
观察者之前调用 promise 中的代码 run on。所以一个简单的:
myPromise().then(function(result){
$scope.someName = result;
});
够了,保持简单。
* 注意角度区分抛出和拒绝 - 默认情况下会记录抛出,并且必须明确记录拒绝