9

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

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

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

是否可以针对以下方面的特定方面提供详细的答案:

  1. 在您描述的每个步骤/阶段中发生的事情的顺序是什么
  2. 何时创建具有新承诺实例的新延迟对象 - 谁知道它/它重要吗?
  3. 当 promise 对象被解析时,范围是如何更新的?我是否必须在回调中手动更新它,否则摘要将被自动调用并更新 rootScope就像在这里声明的一样
  4. 至少提到一种从 Promise 回调中更新范围的方法
  5. 我认为还有很多其他有用的方面,请随时提供它们。

我会感谢并接受最详细的答案,并尽可能多地引用文档或源代码(我自己找不到)。我找不到任何与此主题的先前讨论,如果已经存在的话 - 请发布链接。

ps:对于任何可以通过为此问题提出更好的标题来提供帮助的人+1,请在评论中添加您的建议。

干杯!

4

2 回答 2

25

Promise 具有三种状态

  • 待定- 这就是承诺的开始方式。
  • Fulfilled - 当你解决一个 deferred 或者从.thenfulfill 的返回值时会发生这种情况,它通常类似于标准返回值。
  • Rejected - 当你拒绝一个延迟的,当你throw从一个.then处理程序中,或者当你返回一个解包为拒绝的承诺时会发生这种情况*,它通常类似于抛出的标准异常。

$rootScope.$evalAsync(callback);在 Angular 中,promise 是异步解析的,并通过解析 via (取自此处)来提供保证。

由于它是通过运行的,$evalAsync我们知道在 Promise 解决后(通常)至少会发生一个摘要循环,因为如果一个新摘要不在进行中,它将安排一个新的摘要。

这也是为什么例如当你想在 Angular 中对 Promise 代码进行单元测试时,你需要运行一个摘要循环(通常是在rootScopevia 上$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;
});

够了,保持简单。

* 注意角度区分抛出和拒绝 - 默认情况下会记录抛出,并且必须明确记录拒绝

于 2014-04-29T11:23:31.697 回答
8

当承诺解决时 - 它调用摘要循环?

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

{{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. 调用 tosetTimeout而不是$timeout故意的,以确保它必须resolve最终启动摘要循环。

这可以通过查看源代码来确认:resolve通过 调用其回调nextTick,这是一个将回调传递给的函数,$rootScope.$evalAsync根据$evalAsync文档

表达式执行后至少会执行一个 $digest 循环

那些相同的文档还说:

注意:如果在 $digest 循环之外调用此函数,将安排一个新的 $digest 循环

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


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

详细进入前面的示例:

  1. var d = $q.defer(); 延迟对象的承诺处于待处理状态。在这一点上,几乎没有发生任何事情,你只有一个具有resolvereject和属性notifiy的延迟对象。promise没有使用或影响 $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(通过 nextTick)

  4. $evalAsync 将调用回调

  5. 然后 $evalAsync 将确保调用一个 $digest 循环。


'2。何时创建具有新承诺实例的新延迟对象 - 谁知道它/它重要吗?

只有调用者$q.defer()resolved在被调用(或实际上,rejectnotify)之前,任何范围都不会发生任何事情。


'3。范围更新的承诺对象究竟是如何解决的?我是否必须在回调中手动更新它,否则摘要将被自动调用并更新 rootScope 就像在这里声明的一样

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


'4。至少提到一种从 Promise 回调中更新范围的方法

上面的例子给出了这个。


'5。我认为还有很多其他有用的方面,请随时提供它们。

不是我能想到的!

于 2014-04-29T13:00:22.693 回答