使用 Firefox 打开确认对话框时显示“$apply already in progress”
"$apply already in progress" when opening confirm dialog box with Firefox
我(有时)在以下看似无辜的情况下打开确认对话框时遇到奇怪的 $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 already in progress 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 关于这个错误(以及其他 Whosebug 问题),但我不明白这种情况如何适用,因为我没有调用 $apply 也没有直接与 DOM.
经过一些分析,这似乎是 Firefox 中 $interval 和模态对话框之间令人惊讶的交互。
有什么问题吗?
调用堆栈显示一些奇怪的东西:在控制器的 start 函数中调用了 AngularJS 函数 tick。这怎么可能?
好吧,似乎 Firefox does not suspend the timeout/interval functions when displaying a modal dialog box:这允许在当前正在执行的 javascript 代码 的顶部调用配置的超时和间隔回调 。
在上述情况下,start 函数使用 $apply 序列调用(由 AngularJS 在按钮被点击)并且当 $interval 回调在 start 函数顶部执行时,第二个 $apply 序列启动(由 AngularJS)=> 繁荣,引发了一个 $apply already in progress 错误。
可能的解决方案
定义一个新的 confirm 服务(改编自 this and that 博文):
// 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;
});
});
};
此解决方案之所以有效,是因为模态对话框是在间隔的回调执行中打开的,并且(我相信)interval/timeout 执行由 javascript VM 序列化。
我在 Firefox 中遇到了同样的问题。使用 window.confirm
而不仅仅是 confirm
为我修复了它。
我(有时)在以下看似无辜的情况下打开确认对话框时遇到奇怪的 $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 already in progress 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 关于这个错误(以及其他 Whosebug 问题),但我不明白这种情况如何适用,因为我没有调用 $apply 也没有直接与 DOM.
经过一些分析,这似乎是 Firefox 中 $interval 和模态对话框之间令人惊讶的交互。
有什么问题吗?
调用堆栈显示一些奇怪的东西:在控制器的 start 函数中调用了 AngularJS 函数 tick。这怎么可能?
好吧,似乎 Firefox does not suspend the timeout/interval functions when displaying a modal dialog box:这允许在当前正在执行的 javascript 代码 的顶部调用配置的超时和间隔回调 。
在上述情况下,start 函数使用 $apply 序列调用(由 AngularJS 在按钮被点击)并且当 $interval 回调在 start 函数顶部执行时,第二个 $apply 序列启动(由 AngularJS)=> 繁荣,引发了一个 $apply already in progress 错误。
可能的解决方案
定义一个新的 confirm 服务(改编自 this and that 博文):
// 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;
});
});
};
此解决方案之所以有效,是因为模态对话框是在间隔的回调执行中打开的,并且(我相信)interval/timeout 执行由 javascript VM 序列化。
我在 Firefox 中遇到了同样的问题。使用 window.confirm
而不仅仅是 confirm
为我修复了它。