diff --git a/src/Scope.js b/src/Scope.js index be5030cc2db8..c4b9513b30c3 100644 --- a/src/Scope.js +++ b/src/Scope.js @@ -260,7 +260,8 @@ Scope.prototype = { watcher = { fn: listenFn, last: Number.NaN, // NaN !== NaN. We used this to force $watch to fire on first run. - get: get + get: get, + exp: watchExp }; if (!array) { @@ -325,7 +326,8 @@ Scope.prototype = { asyncQueue, length, dirty, ttl = 100, - next, current, target = this; + next, current, target = this, + watchLog = []; if (target.$$phase) { throw Error(target.$$phase + ' already in progress'); @@ -356,6 +358,14 @@ Scope.prototype = { dirty = true; watch.last = copy(value); watch.fn(current, value, last); + if (ttl < 5) { + if (!watchLog[4-ttl]) watchLog[4-ttl] = []; + if (isFunction(watch.exp)) { + watchLog[4-ttl].push('fn: ' + (watch.exp.name || watch.exp.toString())); + } else { + watchLog[4-ttl].push(watch.exp); + } + } } } catch (e) { current.$service('$exceptionHandler')(e); @@ -376,7 +386,8 @@ Scope.prototype = { } while ((current = next)); if(!(ttl--)) { - throw Error('100 $digest() iterations reached. Aborting!'); + throw Error('100 $digest() iterations reached. Aborting!\n' + + 'Watchers fired in the last 5 iterations: ' + toJson(watchLog)); } } while (dirty); }, diff --git a/test/ScopeSpec.js b/test/ScopeSpec.js index b1942646b7ac..e1f8181e285d 100644 --- a/test/ScopeSpec.js +++ b/test/ScopeSpec.js @@ -218,14 +218,34 @@ describe('Scope', function() { }); - it('should prevent infinite recursion', function() { - root.$watch('a', function(self, v){self.b++;}); - root.$watch('b', function(self, v){self.a++;}); + it('should prevent infinite recursion and print watcher expression', function() { + root.$watch('a', function(self){self.b++;}); + root.$watch('b', function(self){self.a++;}); root.a = root.b = 0; expect(function() { root.$digest(); - }).toThrow('100 $digest() iterations reached. Aborting!'); + }).toThrow('100 $digest() iterations reached. Aborting!\n'+ + 'Watchers fired in the last 5 iterations: ' + + '[["a","b"],["a","b"],["a","b"],["a","b"],["a","b"]]'); + }); + + + it('should prevent infinite recurcion and print print watcher function name or body', + function() { + root.$watch(function watcherA() {return root.a;}, function(self){self.b++;}); + root.$watch(function() {return root.b;}, function(self){self.a++;}); + root.a = root.b = 0; + + expect(function() { + root.$digest(); + }).toThrow('100 $digest() iterations reached. Aborting!\n'+ + 'Watchers fired in the last 5 iterations: ' + + '[["fn: watcherA","fn: function () {return root.b;}"],'+ + '["fn: watcherA","fn: function () {return root.b;}"],'+ + '["fn: watcherA","fn: function () {return root.b;}"],'+ + '["fn: watcherA","fn: function () {return root.b;}"],'+ + '["fn: watcherA","fn: function () {return root.b;}"]]'); });