AngularJS:$ q - >事物的延迟API顺序(生命周期)和谁调用摘要?

时间:2014-04-29 11:11:40

标签: angularjs angularjs-scope promise deferred angular-promise

$q service在angularjs中非常强大,使我们的异步代码生活更轻松。

我是角色的新手,但使用延迟API对我来说并不是什么新鲜事。我必须说我完全没有文档的How to use部分+文档中有非常有用的链接+我检查了源代码。

我的问题更多的是关于引擎盖部分的延迟和承诺API对象的角度。 生命周期中的确切阶段是什么?它们如何与rootScope.Scope(s)进行交互。我的假设是,当promise解决时 - 它会调用摘要循环???是/否?

可以针对以下方面列表提供详细的答案:

  1. 每个描述的事情的顺序是什么 步骤/阶段
  2. 当创建了新的promise实例的新延迟对象时 - 谁知道它/是它 重要吗?
  3. 当promise对象被解析时,范围是如何更新的?我是否必须在回调中手动更新它,否则将自动调用摘要并更新rootScope like declared here
  4. 提及至少一种从promise回调中更新范围的方法
  5. 我认为还有很多其他有用的方面,请随意提供。
  6. 我会欣赏并接受最详细的答案,尽可能多地引用文档或来源(我自己无法找到)。我无法找到之前有关此主题的讨论,如果已经有 - 请发布链接。

    ps:对于通过为此问题建议更好标题而有所帮助的+1,请在评论中添加您的建议。

    干杯!

2 个答案:

答案 0 :(得分:24)

承诺有三个州

  • 待定 - 这就是承诺的开始。
  • 已实现 - 这是您解决延迟时或.then的返回值达到时所发生的情况,并且通常类似于标准返回值。
  • 拒绝 - 当您拒绝延期时,当您从throw处理程序.then或当您返回解除拒绝承诺*的承诺时,会发生这种情况,它通常类似于抛出的标准异常。

在Angular中,promises异步解析并通过$rootScope.$evalAsync(callback);(取自here)解析来提供保证。

由于它是通过$evalAsync运行的,因此我们知道在promise(通常)解析后至少会发生一个摘要周期,因为如果没有进行摘要,它将安排一个新的摘要。

这也是为什么例如当你想在Angular中单元测试promise代码时,你需要运行一个摘要循环(通常在rootScope上通过$rootScope.digest()),因为$ evalAsync执行是摘要循环。

好的,足够的谈话,告诉我代码:

注意:这显示了来自Angular 1.2的代码路径,Angular 1.x中的代码路径都相似但是在1.3+ $ q中已经重构为使用原型继承所以这个答案不是这些版本的代码准确(但在精神上)。

1)当创建$ q时,它会this

  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上运行,直到它为空,在您的承诺中执行代码。

因此,正如我们所看到的,更新范围只是分配给它,如果摘要尚未运行,它将运行,如果是,则在$evalAsync上运行的promise中的代码被调用观察者正在奔跑。这很简单:

myPromise().then(function(result){
    $scope.someName = result;
});

完美,保持简单。

* note angular将抛出与拒绝区分开来 - 默认情况下会记录抛出并且必须明确记录拒绝

答案 1 :(得分:8)

  

当promise解析时 - 它会调用摘要循环吗?

是。您可以通过一个简单的模板进行测试:

{{state}}

和在延迟后更改$scope.state变量的控制器代码:

$scope.state = 'Pending';
var d = $q.defer();
d.promise.then(function() {
  $scope.state = 'Resolved, and digest cycle must have run';
});
$window.setTimeout(function() {
  d.resolve();
}, 1000);

您可以在http://plnkr.co/edit/fIfHYz9EYK14A5OS6NLd?p=preview看到此消息。一秒钟后,HTML中的文字显示Resolved, and digest cycle must have run。对setTimeout而不是$timeout的调用是故意的,以确保它必须是resolve,最终会启动摘要循环。

通过查看来源确认了这一点:resolve通过nextTick调用其回调,这是一个将回调传递给$rootScope.$evalAsync的函数,根据$evalAsync docs }:

  

表达式执行后将执行至少一个$摘要周期

Those same docs也说:

  

注意:如果在$ digest周期之外调用此函数,将安排新的$ digest周期

所以堆栈是否已经在$ digest循环中可以改变事件的确切顺序。


  

&#39; 1。在您描述的每个步骤/阶段中发生的事情的顺序是什么

详细介绍上一个例子:

  1. var d = $q.defer();  延迟对象的承诺处于暂挂状态。此时,几乎没有任何事情发生,您只有一个具有resolverejectnotifiypromise属性的延迟对象。没有使用或影响$ digest循环

  2. d.promise.then(function() { $scope.state = 'Resolved, and digest cycle must have run'; });

    承诺仍处于暂挂状态,但注册了then次成功回调。同样,没有使用或影响$ digest循环或任何范围。

  3. $window.setTimeout(function() { d.resolve(); }, 1000);

    1秒后,将调用d.resolve。这会将上面步骤2中定义的回调传递给$evalAsync (via nextTick)

  4. $ evalAsync将调用回调

  5. $ evalAsync将确保调用一个$ digest周期。


  6.   

    &#39; 2。当新的deferred对象创建了一个新的promise实例时 - 谁知道它/它是否重要?

    仅限$q.defer()的来电者。在resolved被调用(或者确实是rejectnotify)之前,任何范围都没有任何变化。


      

    &#39; 3。范围更新的承诺对象是如何解析的?我是否必须在回调中手动更新它,否则将自动调用摘要并更新rootScope,就像在此声明一样

    如上所述,$ digest循环将通过调用resolve自动启动(如果它已经不在其中)。


      

    &#39; 4。提到至少一种从promise回调中更新范围的方法

    上面的例子给出了这个。


      

    &#39; 5。我假设有很多其他有用的方面,随时提供它们。

    不是我能想到的!