如果$http.get()没有返回新数据,如何让AngularJS跳过运行digest循环

14

我目前正在查询服务器以检查是否有新数据,然后相应地更新AngularJS应用程序中的模型。以下大致是我的操作:

setInterval(function () {
    $http.get('data.json').then(function (result) {
        if (result.data.length > 0) {
          // if data, update model here
        } else {
          // nothing has changed, but AngularJS will still start the digest cycle
        }
    });
}, 5000);

这个方法可以正常运行,但大部分请求并没有返回任何新数据或数据更改,但是$http服务并不知道/关心此事,仍然会触发digest循环。我认为这是不必要的(因为digest循环是应用程序中最重的操作之一)。 有没有办法仍然使用$http但如果没有任何更改就跳过digest循环?

一个解决方案是不使用$http而是使用jQuery,然后调用$apply使Angular知道模型已经改变:

setInterval(function () {
    $.get('data.json', function (dataList) {
        if (dataList.length > 0) {
            // if data, update model
            $scope.value = dataList[0].value + ' ' + new Date();

            // notify angular manually that the model has changed.
            $rootScope.$apply();
        }
    });
}, 5000);

虽然这个方法似乎可行,但我不确定它是否是一个好主意。如果可能的话,我仍然想使用纯Angular。

有人对上述方法有改进建议或更高效的解决方案吗?

附言:我之所以使用setInterval而不是$timeout是因为$timeout也会触发$digest循环,在这种情况下是不必要的,并且只会加剧“问题”。


抱歉打扰您,但是您在哪里读到的 $http 触发了一个 digest 循环(如果您没有将数据放入作用域)? - Whisher
1
$http 会触发 $apply,从而引发一个 digest 循环。 - asgoth
2
我发现$timeout有一个可选参数叫做“invokeApply”,如果设置为false,将跳过digest循环。如果$http也提供相同的选项,那么这是有意义的。 - Strille
遇到了类似的问题。在进行 $http 调用并更新原始对象后,与其绑定的视图(输入框)得到了更新,但是原始对象属性最终又变成了未定义状态。 我唯一额外添加的是一个 ng-change 事件绑定到这个输入框上。这个 ng-change 事件也会在 http 请求之后自动触发。 有人能提供一些建议吗? - Saurabh Tiwari
3个回答

7
  1. AngularJS提供的解决方案 @doc

AngularJS建议使用一种PERF技巧,通过$httpProvider将几个$http响应捆绑在一个$digest中。但这并不能解决问题,只是起到一个镇定作用 :)

$httpProvider.useApplyAsync(true)

  1. 保存$$watchers的解决方案-风险高且不可扩展

首先,接受的解决方案不可扩展-在100K行JS代码项目上不可能使用$watchers技巧-这是不可能的。

其次,即使项目很小,它也相当危险!例如,如果另一个需要这些监视器的ajax调用到达会发生什么?


  1. 另一种(可行的)风险解决方案

实现此目标的唯一替代方法而不修改AngularJS代码将是将$rootScope.$$phase设置为true或'$digest',进行$http调用,然后将$rootScope.$$phase设置回null。

 $rootScope.$$phase = true;
 $http({...})
   .then(successcb, failurecb)
   .finally(function () {
       $rootScope.$$phase = null;            
   });

风险:

1) 其他ajax调用可能会尝试做同样的事情-->它们需要通过包装ajax服务(超过$http)同步。

2) 用户可以在其中触发UI操作,这些操作将使$$phase更改为null,当ajax调用返回时,仍然会触发$digest。

扫描AngularJS源代码后,解决方案出现了 - 这是拯救局面的那一行:https://github.com/angular/angular.js/blob/e5e0884eaf2a37e4588d917e008e45f5b3ed4479/src/ng/http.js#L1272


  1. 理想的解决方案

因为这是每个使用AngularJS的人都面临的问题,我认为它需要系统地解决。以上的答案没有解决问题,只是试图避免它。 因此,我们应该创建一个AngularJS拉取请求,允许我们通过$httpProvider指定配置,不会为特定的$http请求触发digest。希望他们同意这需要以某种方式加以解决。


1
你可以用这个技巧来做:

var watchers;

scope.$on('suspend', function () {
  watchers = scope.$$watchers;
  scope.$$watchers = [];
});

scope.$on('resume', function () {
  scope.$$watchers = watchers;
  watchers = null;
});

使用此方法,您可以在$digest周期内删除或重新插入作用域。

当然,您需要管理事件来实现这一点。

请参考此帖子:

从digest周期中删除和恢复作用域

希望对您有所帮助!


有趣。你是说如果 $http 没有产生任何变化,我可以“暂停”根作用域,然后在 $http 完成后以某种方式“恢复”rootScope吗?修改 $$watchers,这不是 Angular 的公共 API 的一部分,感觉有点不对。但我学到了新东西! - Strille
你可以这样做,但这是一种“hack”的方式!打破摘要循环是改变Angular哲学的做法,你永远无法通过AngularJS API获得优雅的解决方案!这就像检查$$phase以了解摘要或应用程序循环是否正在进行一样! - Thomas Pons

1
Web sockets似乎是这里最优雅的解决方案。这样你就不需要轮询服务器了。服务器可以告诉你的应用程序数据或任何内容何时发生了变化。

我同意Web Sockets是最优雅的解决方案,但目前在我们的项目中不太可能发生。轮询在这种情况下运作良好,非常稳定和可预测。 - Strille
@Strille,让循环周期运行需要太长时间吗?即使看起来有些浪费,如果它不会拖慢您的应用程序,那么这是一个大问题吗? - powerc9000
它并不需要太多时间,但这是一个旨在全天在移动设备上运行的应用程序,我希望不必要地运行大量JavaScript以节省电池。但你说得对,这不是什么大问题。 - Strille

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接