AngularJS报错$apply already in progress的解决方法分析


Posted in Javascript onJanuary 30, 2017

本文实例分析了AngularJS报错$apply already in progress的解决方法。分享给大家供大家参考,具体如下:

如果我们使用了AngularJS中的$scope.$apply()或者$scope.$digest(),我们很可能会遇到类似下面的错误,虽然这个错误没有太大影响,但是在日志中看起来还是很不爽的,日志中记录的异常或者错误,就应该是需要关注和解决的问题,否则就没有必要出现在日志中了。

Error: [$rootScope:inprog] $apply already in progress
http://errors.angularjs.org/1.3.13/$rootScope/inprog?p0=%24apply
  at angular.js:63
  at beginPhase (angular.js:14755)
  at Scope.$apply (angular.js:14499)
  at new <anonymous> (1%20-%20%E5%89%AF%E6%9C%AC.html:10)
  at Object.invoke (angular.js:4185)
  at extend.instance (angular.js:8454)
  at angular.js:7700
  at forEach (angular.js:331)
  at nodeLinkFn (angular.js:7699)
  at compositeLinkFn (angular.js:7078)

下面这段代码就会报上面这个错误:

var myModule = angular.module('myModule', []);
myModule.controller("ctrl_1",function($scope){
$scope.value = "aty";
$scope.$apply();
//$scope.$digest();
});

这个错误其实很好理解:angularJS框架本身已经在做脏数据检测了,我们没有必要再手动调用$apply或者$digest。这里自然而然出现了一个疑问:什么时候需要我们手动调用$apply或者$digest,什么时候不需要呢?这是个好问题,我现在也不知道,目前我只能列出自己在项目中遇到的2种需要手动调用$apply的情况。

情况1:controller中如果有异步操作,比如ajax回调,timeout延时等。可以这么理解:由于异步(延迟)的存在,当开始执行回调函数的时候,angularJS自身controller中的脏值检测已经结束,无法检测到回调函数导致数据的变化。

<html>
 <head>
  <script src="jquery-1.11.1.min.js"></script>
  <script src="angular.js"></script>
  <script>
    var myModule = angular.module('myModule', []);
    myModule.controller("ctrl_1",function($scope){
      $scope.text = "place";
      setTimeout(function(){
        $scope.text = "value setted after time out";
        $scope.$apply();//必需手动进行脏值检测,否则数据无法刷新到界面
      },1000);
    });
    $(function(){
      angular.bootstrap($("#div1")[0], ["myModule"]);
    })
  </script>
 </head>
 <body>
  <div id="div1" ng-controller="ctrl_1">
    <div>{{text}}</div>
    <input id="btn" type="button" value="jquery-event"></input>
  </div>
 </body>
</html>

这段代码如果没有调用$scope.$apply()的话,数据是不会刷新到界面上的。

情况2:在jQuery代码中修改$scope中的数据。这种情况是在angular框架之外操作$scope中的数据,angular不能检测到数据变化是正常的。

<html>
 <head>
  <script src="jquery-1.11.1.min.js"></script>
  <script src="angular.js"></script>
  <script>
    var myModule = angular.module('myModule', []);
    myModule.controller("ctrl_1",function($scope){
      $scope.text = "place";
    });
    $(function(){
      angular.bootstrap($("#div1")[0], ["myModule"]);
      $("#btn").click(function(){
        var $scope = $("#btn").scope();
        $scope.text = "value setted in jquery";
        $scope.$apply();
      });
    })
  </script>
 </head>
 <body>
  <div id="div1" ng-controller="ctrl_1">
    <div>{{text}}</div>
    <input id="btn" type="button" value="jquery-event"></input>
  </div>
 </body>
</html>

在JQuery的事件处理函数中,我们能够通过dom拿到其关联的$scope对象,进而修改$scope中的数据。这种情况下,也必需要手动调用$scope.$apply()。

也就是说我们必须要清楚哪儿些情况需要手动$apply,哪儿些情况不需要手动$apply,这看起来很简单,实际上并不是这样的。看一段我们项目中的代码:

var myModule = angular.module('myModule', []);
myModule.controller("ctrl_1",function($scope){
  $scope.listItems = [];
  $scope.loadListFromService = function(){
    Spl.MessageProcessor.loadData({
          serviceId : "url",
          data : {},
          success : function(json) {
            $scope.listItems = json.results;
      // 要不要$scope.$apply()?
          },
          error: function() {
            console.error("invoke service["+optionsJson.serviceId+"] error.");
          }
    });
  }
  $scope.loadListFromService();
});

loadData()这个函数很像ajax回调,确实是这样的,这个API不过是做了一点封装,大致代码如下:

function loadData(options)
{
  // 从本地缓存中读取,很快
  var dataInCache = U.loadDataFromCache(options.serviceId);
  if(dataInCache)
  {
    options.success(dataInCache);
  }
  else
  {
    //异步ajax
    U.readDataFromServer(options.serviceId, options.data, function(response){
      options.success(response);
    });
  }
}

由于缓存的影响,$scope.loadListFromService()变得不再那么可控,如果本地已经有缓存了,那直接读取缓存是很快的,这个时候不需要手动$apply;如果是第一次,本地没有缓存,那么就变成了情况1,我们需要手动$apply。显然调用loadData()函数的地方,不需要也不应该关注到底有没有缓存的存在,这个时候判断需要不要手动$apply就没有那么容易的。简单粗暴的方式,不管怎么样都手动调用下$scope.$apply(),这样功能不会有问题,但是日志中就不可避免要出现文中开头提到的错误。

angular中的$scope中提供了一个$$phase变量,如果这个变量的值是"$digest" 或者"$apply",就代表angular自身已经在做脏值检测了,不需要我们再去调用$apply或者$digest;否则的话就需要我们手动调用$apply或者$digest了。利用这个属性,我们就可以方便解决上面的错误了,判断一下呗。下面是一个工具函数,好理解吧。

function safeApply(scope, fn) {
  (scope.
phase||scope.$root.
phase) ? fn() : scope.$apply(fn);
}

最后提一下,$digest、$apply、$$phase这些属性或者方法其实都是$scope中的私有的,最好不要使用。如果你用到了这些方法,基本可以断言你的代码存在问题,没有按照angular的方式来组织代码。比如情况1中的setTimeout,完全可以用angular中的$timeout代替,这才是推荐的方式,而不是通过$apply来补救。

<html>
 <head>
  <script src="jquery-1.11.1.min.js"></script>
  <script src="angular.js"></script>
  <script>
    var myModule = angular.module('myModule', []);
    myModule.controller("ctrl_1",function($scope, $timeout){
      $scope.text = "place";
      $timeout(function(){
        $scope.text = "value setted after time out";
      },1000);
    });
    $(function(){
      angular.bootstrap($("#div1")[0], ["myModule"]);
    })
  </script>
 </head>
 <body>
  <div id="div1" ng-controller="ctrl_1">
    <div>{{text}}</div>
    <input id="btn" type="button" value="jquery-event"></input>
  </div>
 </body>
</html>

所以解决“$apply already in progress”最好的方式,就是不要使用$scope.$apply()或者$scope.$digest()。

希望本文所述对大家AngularJS程序设计有所帮助。

Javascript 相关文章推荐
用Javascript 和 CSS 实现脚注(Footnote)效果
Sep 09 Javascript
用表格输出1-1000之间的数字实现代码(附特效)
Apr 21 Javascript
JQuery控制div外点击隐藏而div内点击不会隐藏的方法
Jan 13 Javascript
JavaScript监听文本框回车事件并过滤文本框空格的方法
Apr 16 Javascript
AngularJS中directive指令使用之事件绑定与指令交互用法示例
Nov 22 Javascript
JS实现简单的二元方程计算器功能示例
Jan 03 Javascript
echart简介_动力节点Java学院整理
Aug 11 Javascript
一步步教你利用webpack如何搭一个vue脚手架(超详细讲解和注释)
Jan 08 Javascript
动态Axios的配置步骤详解
Jan 12 Javascript
node.js的http.createServer过程深入解析
Jun 06 Javascript
微信小程序 高德地图路线规划实现过程详解
Aug 05 Javascript
vue.js实现h5机器人聊天(测试版)
Jul 16 Javascript
JavaScript基础之AJAX简单的小demo
Jan 29 #Javascript
JavaScript Date 知识浅析
Jan 29 #Javascript
JavaScript实现时钟滴答声效果
Jan 29 #Javascript
Javascript中 带名 匿名 箭头函数的重要区别(推荐)
Jan 29 #Javascript
jQuery图片轮播功能实例代码
Jan 29 #Javascript
jQuery代码实现实时获取时间
Jan 29 #Javascript
用director.js实现前端路由使用实例
Jan 27 #Javascript
You might like
检测png图片是否完整的php代码
2010/09/06 PHP
php使用websocket示例详解
2014/03/12 PHP
Joomla简单判断用户是否登录的方法
2016/05/04 PHP
[原创]php简单防盗链验证实现方法
2016/07/09 PHP
PHP页面静态化――纯静态与伪静态用法详解
2020/06/05 PHP
动态修改DOM 里面的 id 属性的弊端分析
2008/09/03 Javascript
JQuery1.4+ Ajax IE8 内存泄漏问题
2010/10/15 Javascript
javascript中删除指定数组中指定的元素的代码
2011/02/12 Javascript
Javascript闭包用法实例分析
2015/01/23 Javascript
在JS方法中返回多个值的方法汇总
2015/05/20 Javascript
JavaScript保留关键字汇总
2015/12/01 Javascript
jquery中cookie用法实例详解(获取,存储,删除等)
2016/01/04 Javascript
Bootstrap进度条组件知识详解
2016/05/01 Javascript
BootStrap下拉框在firefox浏览器界面不友好的解决方案
2016/08/18 Javascript
JavaScript获取tr td 的三种方式全面总结(推荐)
2017/08/15 Javascript
浅谈vue单一组件下动态修改数据时的全部重渲染
2018/03/01 Javascript
vue移动端项目缓存问题实践记录
2018/10/29 Javascript
微信小程序在线客服自动回复功能(基于node)
2019/07/03 Javascript
微信小程序wx.request的简单封装
2019/11/13 Javascript
vue+echarts+datav大屏数据展示及实现中国地图省市县下钻功能
2020/11/16 Javascript
Python3实现统计单词表中每个字母出现频率的方法示例
2019/01/28 Python
OpenCV 模板匹配
2019/07/10 Python
python实现把两个二维array叠加成三维array示例
2019/11/29 Python
python绘制雪景图
2019/12/16 Python
捷克移动配件网上商店:ProMobily.cz
2019/03/15 全球购物
房屋改造计划书
2014/01/10 职场文书
节水标语大全
2014/06/11 职场文书
公司门卫岗位职责范本
2014/07/08 职场文书
建筑安全生产目标责任书
2014/07/23 职场文书
2014年银行员工年终自我评价
2014/09/19 职场文书
小学运动会报道稿
2014/10/04 职场文书
街道党工委党的群众路线教育实践活动对照检查材料思想汇报
2014/10/05 职场文书
2014年采购工作总结
2014/11/20 职场文书
维稳工作承诺书
2015/01/20 职场文书
Python 实现定积分与二重定积分的操作
2021/05/26 Python
python异步的ASGI与Fast Api实现
2021/07/16 Python