【问题标题】:Angular Infinite Digest Loop with promise.then()带有 promise.then() 的 Angular 无限摘要循环
【发布时间】:2017-07-05 14:19:11
【问题描述】:

问题:在控制器方法中使用this.promise.then(function(){}) 函数似乎会创建一个 $rootScope:infDig 循环,尽管该方法的返回值不会改变。值得注意的是,当删除 .then() 行时,无限摘要循环消失了(参见代码)。

代码: 视图中的{{ getSelectedProjects() }} 和附加控制器中的以下方法

var vm = this;
vm.getSelectedProjects = function() {
  if (angular.isUndefined(vm.hello)) {
    vm.hello = "hello"
    vm.promise = $q.when("hello i am a resolved promise")
  }
  vm.promise //In my original code, this promise is returned by a service (MyService.getPromise()). The resolved value of the returned promise may change which is why the service needs to be called at each digest cycle.
  .then(function (ans) {
    vm.hello = ans;
  }); //remove .then(...) function and there is no infinite digest loop anymore
  return vm.hello;
}

问题:尽管返回值没有改变,为什么会创建一个无限摘要循环?我怎样才能避免它(我非常需要那个承诺的结果)?。

我摇摇欲坠的想法: 我想这与 .then() 每次返回一个 new 承诺的事实有关。文档没有说明,但是如果每次都将这个承诺附加到控制器对象(vm),这可能会被视为控制器的状态更改并触发一个新的摘要循环,该循环重新评估 vm.getSelectedProjects()并再次将新的承诺附加到控制器等。

编辑 我在“摇摇欲坠的想法”中的假设似乎并不完全正确或完整,因为此代码仍然存在问题:

vm.getSelectedProjects = function() {
  if (angular.isUndefined(vm.hello)) {
    vm.hello = "hello"
  }
  $q.when("hello i am a resolved promise")//this object is not attached to the controller object
  .then(function () {});
  return vm.hello;
}

【问题讨论】:

  • 这是您在控制台中看到的错误docs.angularjs.org/error/$rootScope/infdig 吗?
  • 没错,将其添加到帖子中。
  • 并且循环是通过使用一个承诺创建的,当它解决时 - 一个新的摘要循环开始并且这个函数被一次又一次地调用
  • "$q 与 AngularJS 中的 $rootScope.Scope Scope 模型观察机制集成,"解析触发新的摘要循环
  • 这不是承诺。这是回调。每次执行你的函数时,它都会向 then() 传递一个新的回调,这个回调在摘要循环结束时执行,并触发一个新的回调,它重新评估方法等。只是不要调用服务和从该方法内部注册一个回调到一个承诺。这应该从事件处理程序(单击,无论如何)或您的控制器构造函数中完成。不是来自表达式评估。

标签: angularjs promise


【解决方案1】:

根据遇到的问题和 cmets,我们创建了在项目中使用 Promise 的编码指南。发现分享它很有趣,因为我在任何地方都没有看到这个文档。

编码指南:使用 Promise

使用承诺

Promise 是惊人的对象,但是错误的使用会让人头疼。因此,我们有一些指导方针:

  • 不要在视图中使用承诺
  • 请勿在控制器中使用 Promise,除非在事件处理程序(单击、初始化等)、控制器构造函数(=仅执行一次)或动画中调用的函数中
  • 如果在视图中使用承诺结果,模型(或管理器)应包含默认为空的属性,并在承诺解决时设置为已解决的值。控制器只返回该值。

实施

这种结构应该在模型、管理器或其他服务中,尽可能靠近数据源。不在控制器中:

  var myApiMethod = function() {
    //We call "caching" the fact that this method is executed only if $scope.item is undefined.
    If (angular.isUndefined($scope.item) { 
      $scope.item = {}
      $http(xxx).then(function(value){
        $scope.item = value;
      });
      return $scope.item
    }
  }

然后控制器只返回 myApiMethod 的答案。您可能需要检查 'myService.myApiMethod() != {}'。

在服务中,我们可以定义缓存行为(例如:每 2 小时从后端检索数据,在某个事件时清除缓存等)。只要关于要返回的对象只有一个单一的事实来源(在这种情况下来自服务,而不是来自控制器),您就可以轻松地解决这个问题。

论证

*为什么不在视图或由表达式评估调用的控制器方法中使用承诺(例如,{{ vm.getMyApiMethodResult() }} *:

  • 在视图中包含 Promise 是 Angular 1.x 中已停用的一项功能
  • 如果 myApiMethod 在没有缓存的情况下放在控制器中,然后用于表达式评估(例如,{{ myCtrl.getMyApiMethod() }} ),它将创建一个无限的摘要循环,因为 promise 的解析将触发一个新的摘要循环,它将再次调用 vm.getMyApiMethod() 函数等(更多信息在 this SO answer 中)
  • 为什么不将 myApiMethod 放在带有缓存的控制器中(如上面的实现)以确保 myApiMethod 只被调用一次并避免无限摘要循环?三个原因:

    1. 如果 myApiMethod() 的响应变成一个新对象,$scope.item 将不会被更新。
    2. 另一个缺点(我们过去经常使用,您可能仍然会在代码中看到错误的实现)是它会创建大量重复代码,因为我们需要复制这个非常繁重的代码结构(可能变成在调用 myApiMethod() 的所有控制器中,当涉及多个 Promise 时会更复杂
    3. 如果您在控制器中“缓存”,最终可能会导致不同的控制器返回不同的答案,尽管它们都从同一服务“myApiMethod”返回答案,但时间不同。

记住:本指南仅适用于在表达式评估中调用的控制器函数。您可能需要事件处理程序(点击等)调用的控制器函数中的承诺,这很好,因为这些函数不会在每个 Angular 的摘要周期中调用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-09
    • 2014-04-19
    • 2015-11-09
    • 1970-01-01
    • 2020-09-18
    相关资源
    最近更新 更多