Skip to content

Commit ef875ad

Browse files
committed
feat(scope): better logging of infinite digest error
Feedback team has often problems debugging inifinite digest errors, this change should reveal info about what watchers are causing the infinite loop
1 parent 615841a commit ef875ad

File tree

2 files changed

+38
-7
lines changed

2 files changed

+38
-7
lines changed

Diff for: src/Scope.js

+14-3
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,8 @@ Scope.prototype = {
260260
watcher = {
261261
fn: listenFn,
262262
last: Number.NaN, // NaN !== NaN. We used this to force $watch to fire on first run.
263-
get: get
263+
get: get,
264+
exp: watchExp
264265
};
265266

266267
if (!array) {
@@ -325,7 +326,8 @@ Scope.prototype = {
325326
asyncQueue,
326327
length,
327328
dirty, ttl = 100,
328-
next, current, target = this;
329+
next, current, target = this,
330+
watchLog = [];
329331

330332
if (target.$$phase) {
331333
throw Error(target.$$phase + ' already in progress');
@@ -356,6 +358,14 @@ Scope.prototype = {
356358
dirty = true;
357359
watch.last = copy(value);
358360
watch.fn(current, value, last);
361+
if (ttl < 5) {
362+
if (!watchLog[4-ttl]) watchLog[4-ttl] = [];
363+
if (isFunction(watch.exp)) {
364+
watchLog[4-ttl].push('fn: ' + (watch.exp.name || watch.exp.toString()));
365+
} else {
366+
watchLog[4-ttl].push(watch.exp);
367+
}
368+
}
359369
}
360370
} catch (e) {
361371
current.$service('$exceptionHandler')(e);
@@ -376,7 +386,8 @@ Scope.prototype = {
376386
} while ((current = next));
377387

378388
if(!(ttl--)) {
379-
throw Error('100 $digest() iterations reached. Aborting!');
389+
throw Error('100 $digest() iterations reached. Aborting!\n' +
390+
'Watchers fired in the last 5 iterations: ' + toJson(watchLog));
380391
}
381392
} while (dirty);
382393
},

Diff for: test/ScopeSpec.js

+24-4
Original file line numberDiff line numberDiff line change
@@ -218,14 +218,34 @@ describe('Scope', function() {
218218
});
219219

220220

221-
it('should prevent infinite recursion', function() {
222-
root.$watch('a', function(self, v){self.b++;});
223-
root.$watch('b', function(self, v){self.a++;});
221+
it('should prevent infinite recursion and print watcher expression', function() {
222+
root.$watch('a', function(self){self.b++;});
223+
root.$watch('b', function(self){self.a++;});
224224
root.a = root.b = 0;
225225

226226
expect(function() {
227227
root.$digest();
228-
}).toThrow('100 $digest() iterations reached. Aborting!');
228+
}).toThrow('100 $digest() iterations reached. Aborting!\n'+
229+
'Watchers fired in the last 5 iterations: ' +
230+
'[["a","b"],["a","b"],["a","b"],["a","b"],["a","b"]]');
231+
});
232+
233+
234+
it('should prevent infinite recurcion and print print watcher function name or body',
235+
function() {
236+
root.$watch(function watcherA() {return root.a;}, function(self){self.b++;});
237+
root.$watch(function() {return root.b;}, function(self){self.a++;});
238+
root.a = root.b = 0;
239+
240+
expect(function() {
241+
root.$digest();
242+
}).toThrow('100 $digest() iterations reached. Aborting!\n'+
243+
'Watchers fired in the last 5 iterations: ' +
244+
'[["fn: watcherA","fn: function () {return root.b;}"],'+
245+
'["fn: watcherA","fn: function () {return root.b;}"],'+
246+
'["fn: watcherA","fn: function () {return root.b;}"],'+
247+
'["fn: watcherA","fn: function () {return root.b;}"],'+
248+
'["fn: watcherA","fn: function () {return root.b;}"]]');
229249
});
230250

231251

0 commit comments

Comments
 (0)