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 相关文章推荐
Easy.Ajax 部分源代码 支持文件上传功能, 兼容所有主流浏览器
Feb 24 Javascript
js实现收缩菜单效果实例代码
Oct 30 Javascript
jQuery中get和post方法传值测试及注意事项
Aug 08 Javascript
jQuery支持添加事件的日历特效代码分享(3种样式)
Aug 24 Javascript
JavaScript中this的9种应用场景及三种复合应用场景
Sep 12 Javascript
Bootstrap Table从服务器加载数据进行显示的实现方法
Sep 29 Javascript
Bootstrap简单表单显示学习笔记
Nov 15 Javascript
Vue内容分发slot(全面解析)
Aug 19 Javascript
使用Angular CLI快速创建Angular项目的一些基本概念和写法小结
Apr 22 Javascript
微信小程序实现留言板(Storage)
Nov 02 Javascript
javascript实现搜索筛选功能实例代码
Nov 12 Javascript
vue 自定义的组件绑定点击事件
Apr 21 Vue.js
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
提升PHP执行速度全攻略(上)
2006/10/09 PHP
zf框架的db类select查询器join链表使用示例(zend框架)
2014/03/14 PHP
php中字符集转换iconv函数使用总结
2014/10/11 PHP
PHP中对数组的一些常用的增、删、插操作函数总结
2015/11/27 PHP
Yii2实现ajax上传图片插件用法
2016/04/28 PHP
利用php获得flv视频长度的实例代码
2017/10/26 PHP
tp5.0框架隐藏index.php入口文件及模块和控制器的方法分析
2020/02/11 PHP
php设计模式之备忘模式分析【星际争霸游戏案例】
2020/03/24 PHP
ExtJS下grid的一些属性说明
2009/12/13 Javascript
javascript 正则替换 replace(regExp, function)用法
2010/05/22 Javascript
关于firefox的ElementTraversal 接口 使用说明
2010/11/11 Javascript
Jquery实现自定义弹窗示例
2014/03/12 Javascript
jquery图片轮播特效代码分享
2020/04/20 Javascript
早该知道的7个JavaScript技巧
2016/06/21 Javascript
用v-html解决Vue.js渲染中html标签不被解析的问题
2016/12/14 Javascript
Vue开发过程中遇到的疑惑知识点总结
2017/01/20 Javascript
BootStrap Select清除选中的状态恢复默认状态
2017/06/20 Javascript
JS使用Prim算法和Kruskal算法实现最小生成树
2019/01/17 Javascript
微信小程序实现基于三元运算验证手机号/姓名功能示例
2019/01/19 Javascript
详解Vue 项目中的几个实用组件(ts)
2019/10/29 Javascript
[06:15]2016国际邀请赛中国区预选赛单车采访:我顶WINGS
2016/06/27 DOTA
[01:29:17]RNG vs Liquid 2019国际邀请赛淘汰赛 败者组 BO3 第二场 8.23
2019/09/05 DOTA
Python2中文处理纪要的实现方法
2018/03/10 Python
Python实现绘制双柱状图并显示数值功能示例
2018/06/23 Python
python利用小波分析进行特征提取的实例
2019/01/09 Python
python操作文件的参数整理
2019/06/11 Python
Python 中的 global 标识对变量作用域的影响
2019/08/12 Python
python实现在线翻译功能
2020/03/03 Python
python使用Windows的wmic命令监控文件运行状况,如有异常发送邮件报警
2021/01/30 Python
编写一子程序,将一链表倒序,即使链表表尾变表头,表头变表尾
2016/02/10 面试题
迅雷Cued工作心得体会
2014/01/27 职场文书
护士的自我鉴定
2014/02/07 职场文书
餐厅采购员岗位职责
2014/03/06 职场文书
2014年计生工作总结
2014/11/21 职场文书
php7中停止php-fpm服务的方法详解
2021/05/09 PHP
Redis基于Bitmap实现用户签到功能
2021/06/20 Redis