【问题标题】:"$apply already in progress" when opening confirm dialog box with Firefox使用 Firefox 打开确认对话框时出现“$apply 正在进行中”
【发布时间】:2015-08-06 06:42:22
【问题描述】:

在以下看似无辜的情况下打开确认对话框时,我(有时)会收到一个奇怪的 $apply already in progress 错误:

var mod = angular.module('app', []);

mod.controller('ctrl', function($scope, $interval, $http) {
  
  $scope.started = false;
  $scope.counter = 0;
  
  // some business function that is called repeatedly
  // (here: a simple counter increase)
  $interval(function() {
    $scope.counter++;
  }, 1000);
  
  // this function starts some service on the backend
  $scope.start = function() {
    if(confirm('Are you sure ?')) {
      return $http.post('start.do').then(function (res) {
        $scope.started = true;
        return res.data;
      });
    };
  };

  // this function stops some service on the backend
  $scope.stop = function() {
    if(confirm('Are you sure ?')) {
      return $http.post('stop.do').then(function (res) {
        $scope.started = false;
        return res.data;
      });
    };
  };

});

// mock of the $http to cope with snipset sandbox (irrelevant, please ignore)
mod.factory('$http', function ($q) {
  return {
    post: function() {
      return $q.when({data:null});
    }
  }
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>

<div ng-app="app">
  <div ng-controller="ctrl">
    <button ng-disabled="started" ng-click="start()">Start</button>
    <button ng-disabled="!started" ng-click="stop()">Stop</button>
    <br/><br/>seconds elapsed : {{counter}}
  </div>
</div>

错误信息是:

$rootScope:inprog] $apply 已在进行中 http://errors.angularjs.org/1.2.23/$rootScope/inprog?p0=%24apply

调用堆栈是:

minErr/<@angular.js:78:12
beginPhase@angular.js:12981:1
$RootScopeProvider/this.$get</Scope.prototype.$apply@angular.js:12770:11
tick@angular.js:9040:25
$scope.start@controller.js:153:8
Parser.prototype.functionCall/<@angular.js:10836:15
ngEventHandler/</<@angular.js:19094:17
$RootScopeProvider/this.$get</Scope.prototype.$eval@angular.js:12673:16
$RootScopeProvider/this.$get</Scope.prototype.$apply@angular.js:12771:18
ngEventHandler/<@angular.js:19093:15
jQuery.event.dispatch@lib/jquery/jquery-1.11.2.js:4664:15
jQuery.event.add/elemData.handle@lib/jquery/jquery-1.11.2.js:4333:6

复制:

  • 使用 Firefox(我无法用 Chrome 或 IE 重现它)
  • 打开 JavaScript 控制台
  • 交替单击开始停止按钮(并确认对话框)
  • 尝试多次(10-20x),不容易发生

如果我删除确认对话框,问题就会消失。

我已阅读 AngularJS documentation 关于此错误(以及其他 stackoverflow 问题),但我不知道这种情况如何适用,因为我不调用 $apply 也不直接与 DOM 交互。

【问题讨论】:

  • 尝试像$timeout(function() { /* alert etc. */ });一样包装你的代码
  • 可能与this issue有关。它似乎也与 Firefox 处理 confirm 内点击的方式有关
  • 谢谢@RGraham,我没有看到这个问题,它似乎是相关的,是的。然而,我怀疑 $interval 和 confirm 之间存在一些可疑的交互。

标签: javascript angularjs firefox modal-dialog setinterval


【解决方案1】:

经过一番分析,Firefox 中 $interval 和模态对话框之间的交互似乎令人惊讶。

有什么问题?

调用堆栈显示了一些奇怪的东西:AngularJS 函数 tick 在控制器的 start 函数中被调用。这怎么可能?

好吧,似乎 Firefox does not suspend the timeout/interval functions when displaying a modal dialog box :这允许在当前执行的 javascript 代码之上调用配置的超时和间隔回调

在上述情况下,start 函数被调用一个 $apply 序列(当按钮被点击时由 AngularJS 启动)并且当 $interval 回调在 start 函数的顶部执行,第二个 $apply 序列被启动(由 AngularJS)=> 繁荣,一个 $apply 已经引发了进行中的错误

可能的解决方案

定义一个新的 confirm 服务(改编自 thisthat 博客文章):

// This factory defines an asynchronous wrapper to the native confirm() method. It returns a
// promise that will be "resolved" if the user agrees to the confirmation; or
// will be "rejected" if the user cancels the confirmation.
mod.factory("confirm", function ($window, $q, $timeout) {

    // Define promise-based confirm() method.
    function confirm(message) {
        var defer = $q.defer();

        $timeout(function () {
            if ($window.confirm(message)) {
                defer.resolve(true);
            }
            else {
                defer.reject(false);
            }
        }, 0, false);

        return defer.promise;
    }

    return confirm;
});

...并按以下方式使用它:

// this function starts some service on the backend
$scope.start = function() {
  confirm('Are you sure ?').then(function () {
    $http.post('start.do').then(function (res) {
      $scope.started = true;
    });
  });
};

// this function stops some service on the backend
$scope.stop = function() {
  confirm('Are you sure ?').then(function () {
    $http.post('stop.do').then(function (res) {
      $scope.started = false;
    });
  });
};

此解决方案之所以有效,是因为模式对话框是在间隔的回调执行中打开的,并且(我相信)间隔/超时执行由 javascript VM 序列化。

【讨论】:

    【解决方案2】:

    我在 Firefox 中遇到了同样的问题。使用window.confirm 而不仅仅是confirm 为我修复了它。

    【讨论】:

      猜你喜欢
      • 2014-05-06
      • 2013-03-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-08-07
      • 2016-10-08
      • 1970-01-01
      • 2022-01-19
      相关资源
      最近更新 更多