Skip to content
This repository was archived by the owner on Feb 26, 2024. It is now read-only.

Commit b5d2e9e

Browse files
committed
feat(error): an idea for simple stack
1 parent e11d9ff commit b5d2e9e

File tree

2 files changed

+215
-3
lines changed

2 files changed

+215
-3
lines changed

lib/zone.ts

+64-1
Original file line numberDiff line numberDiff line change
@@ -1664,11 +1664,13 @@ const Zone: ZoneType = (function(global: any) {
16641664
case FrameType.transition:
16651665
if (zoneFrame.parent) {
16661666
// This is the special frame where zone changed. Print and process it accordingly
1667-
frames[i] += ` [${zoneFrame.parent.zone.name} => ${zoneFrame.zone.name}]`;
1667+
// frames[i] += ` [${zoneFrame.parent.zone.name} => ${zoneFrame.zone.name}]`;
16681668
zoneFrame = zoneFrame.parent;
16691669
} else {
16701670
zoneFrame = null;
16711671
}
1672+
frames.splice(i, 1);
1673+
i--;
16721674
break;
16731675
default:
16741676
frames[i] += ` [${zoneFrame.zone.name}]`;
@@ -1834,6 +1836,67 @@ const Zone: ZoneType = (function(global: any) {
18341836
// carefully constructor a stack frame which contains all of the frames of interest which
18351837
// need to be detected and blacklisted.
18361838

1839+
const parentDetectSpec = {name: 'parent'};
1840+
const childDetectZone = Zone.current.fork(parentDetectSpec).fork({
1841+
name: 'child',
1842+
onScheduleTask: function(delegate, curr, target, task) {
1843+
return delegate.scheduleTask(target, task);
1844+
},
1845+
onInvokeTask: function(delegate, curr, target, task, applyThis, applyArgs) {
1846+
return delegate.invokeTask(target, task, applyThis, applyArgs);
1847+
},
1848+
onCancelTask: function(delegate, curr, target, task) {
1849+
return delegate.cancelTask(target, task);
1850+
},
1851+
onInvoke: function(delegate, curr, target, callback, applyThis, applyArgs, source) {
1852+
return delegate.invoke(target, callback, applyThis, applyArgs, source);
1853+
}
1854+
});
1855+
1856+
Error.stackTraceLimit = 100;
1857+
childDetectZone.run(() => {
1858+
childDetectZone.runGuarded(() => {
1859+
const fakeTransitionTo =
1860+
(toState: TaskState, fromState1: TaskState, fromState2: TaskState) => {};
1861+
childDetectZone.scheduleEventTask(
1862+
'detect',
1863+
() => {
1864+
childDetectZone.scheduleMacroTask(
1865+
'detect',
1866+
() => {
1867+
childDetectZone.scheduleMicroTask(
1868+
'detect',
1869+
() => {
1870+
const error = new Error('detect');
1871+
const frames = error.stack.split('\n');
1872+
while (frames.length) {
1873+
let frame = frames.shift();
1874+
blackListedStackFrames[frame] = FrameType.blackList;
1875+
}
1876+
},
1877+
null,
1878+
(t: Task) => {
1879+
(t as any)._transitionTo = fakeTransitionTo;
1880+
t.invoke();
1881+
});
1882+
},
1883+
null,
1884+
(t) => {
1885+
(t as any)._transitionTo = fakeTransitionTo;
1886+
t.invoke();
1887+
},
1888+
() => {});
1889+
},
1890+
null,
1891+
(t) => {
1892+
(t as any)._transitionTo = fakeTransitionTo;
1893+
t.invoke();
1894+
},
1895+
() => {});
1896+
});
1897+
});
1898+
Error.stackTraceLimit = 15;
1899+
18371900
// carefully constructor a stack frame which contains all of the frames of interest which
18381901
// need to be detected and blacklisted.
18391902
let detectRunFn = () => {

test/common/Error.spec.ts

+151-2
Original file line numberDiff line numberDiff line change
@@ -258,12 +258,161 @@ describe('ZoneAwareError', () => {
258258
expect(outsideFrames[0]).toMatch(/testFn.*[<root>]/);
259259

260260
expect(insideFrames[0]).toMatch(/insideRun.*[InnerZone]]/);
261-
expect(insideFrames[2]).toMatch(/testFn.*[<root>]]/);
261+
expect(insideFrames[1]).toMatch(/testFn.*[<root>]]/);
262262

263263
expect(outsideWithoutNewFrames[0]).toMatch(/testFn.*[<root>]/);
264264

265265
expect(insideWithoutNewFrames[0]).toMatch(/insideRun.*[InnerZone]]/);
266-
expect(insideWithoutNewFrames[2]).toMatch(/testFn.*[<root>]]/);
266+
expect(insideWithoutNewFrames[1]).toMatch(/testFn.*[<root>]]/);
267267
}
268268
});
269+
270+
const zoneAwareFrames = [
271+
'Zone.run', 'Zone.runGuarded', 'Zone.scheduleEventTask', 'Zone.scheduleMicroTask',
272+
'Zone.scheduleMacroTask', 'Zone.runTask', 'ZoneDelegate.scheduleTask',
273+
'ZoneDelegate.invokeTask', 'zoneAwareAddListener'
274+
];
275+
276+
function assertStackDoesNotContainZoneFrames(err: Error) {
277+
const frames = err.stack.split('\n');
278+
for (let i = 0; i < frames.length; i++) {
279+
expect(zoneAwareFrames.filter(f => frames[i].indexOf(f) !== -1)).toEqual([]);
280+
}
281+
};
282+
283+
const errorZoneSpec = {
284+
name: 'errorZone',
285+
done: <() => void>null,
286+
onHandleError:
287+
(parentDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, error: Error) => {
288+
assertStackDoesNotContainZoneFrames(error);
289+
setTimeout(() => {
290+
errorZoneSpec.done && errorZoneSpec.done();
291+
}, 0);
292+
return false;
293+
}
294+
};
295+
296+
const errorZone = Zone.root.fork(errorZoneSpec);
297+
298+
const assertStackDoesNotContainZoneFramesTest = function(testFn: Function) {
299+
return function(done: () => void) {
300+
errorZoneSpec.done = done;
301+
errorZone.run(testFn);
302+
};
303+
};
304+
305+
describe('Error stack', () => {
306+
it('Error with new which occurs in setTimeout callback should not have zone frames visible',
307+
assertStackDoesNotContainZoneFramesTest(() => {
308+
setTimeout(() => {
309+
throw new Error('timeout test error');
310+
}, 10);
311+
}));
312+
313+
it('Error without new which occurs in setTimeout callback should not have zone frames visible',
314+
assertStackDoesNotContainZoneFramesTest(() => {
315+
setTimeout(() => {
316+
throw Error('test error');
317+
}, 10);
318+
}));
319+
320+
it('Error with new which cause by promise rejection should not have zone frames visible',
321+
(done) => {
322+
const p = new Promise((resolve, reject) => {
323+
reject(new Error('test error'));
324+
});
325+
p.catch(err => {
326+
assertStackDoesNotContainZoneFrames(err);
327+
done();
328+
});
329+
});
330+
331+
it('Error without new which cause by promise rejection should not have zone frames visible',
332+
(done) => {
333+
const p = new Promise((resolve, reject) => {
334+
reject(Error('test error'));
335+
});
336+
p.catch(err => {
337+
assertStackDoesNotContainZoneFrames(err);
338+
done();
339+
});
340+
});
341+
342+
it('Error with new which occurs in eventTask callback should not have zone frames visible',
343+
assertStackDoesNotContainZoneFramesTest(() => {
344+
const task = Zone.current.scheduleEventTask('errorEvent', () => {
345+
throw new Error('test error');
346+
}, null, () => null, null);
347+
task.invoke();
348+
}));
349+
350+
it('Error without new which occurs in eventTask callback should not have zone frames visible',
351+
assertStackDoesNotContainZoneFramesTest(() => {
352+
const task = Zone.current.scheduleEventTask('errorEvent', () => {
353+
throw Error('test error');
354+
}, null, () => null, null);
355+
task.invoke();
356+
}));
357+
358+
it('Error with new which occurs in longStackTraceZone should not have zone frames and longStackTraceZone frames visible',
359+
assertStackDoesNotContainZoneFramesTest(() => {
360+
const task = Zone.current.fork((Zone as any)['longStackTraceZoneSpec'])
361+
.scheduleEventTask('errorEvent', () => {
362+
throw new Error('test error');
363+
}, null, () => null, null);
364+
task.invoke();
365+
}));
366+
367+
it('Error without new which occurs in longStackTraceZone should not have zone frames and longStackTraceZone frames visible',
368+
assertStackDoesNotContainZoneFramesTest(() => {
369+
const task = Zone.current.fork((Zone as any)['longStackTraceZoneSpec'])
370+
.scheduleEventTask('errorEvent', () => {
371+
throw Error('test error');
372+
}, null, () => null, null);
373+
task.invoke();
374+
}));
375+
376+
it('stack frames of the callback in user customized zoneSpec should be kept',
377+
assertStackDoesNotContainZoneFramesTest(() => {
378+
const task = Zone.current.fork((Zone as any)['longStackTraceZoneSpec'])
379+
.fork({
380+
name: 'customZone',
381+
onScheduleTask: (parentDelegate, currentZone, targetZone, task) => {
382+
return parentDelegate.scheduleTask(targetZone, task);
383+
},
384+
onHandleError: (parentDelegate, currentZone, targetZone, error) => {
385+
parentDelegate.handleError(targetZone, error);
386+
const containsCustomZoneSpecStackTrace =
387+
error.stack.indexOf('onScheduleTask') !== -1;
388+
expect(containsCustomZoneSpecStackTrace).toBeTruthy();
389+
return false;
390+
}
391+
})
392+
.scheduleEventTask('errorEvent', () => {
393+
throw new Error('test error');
394+
}, null, () => null, null);
395+
task.invoke();
396+
}));
397+
398+
it('should be able to generate zone free stack even NativeError stack is readonly', function() {
399+
const _global: any =
400+
typeof window === 'object' && window || typeof self === 'object' && self || global;
401+
const NativeError = _global['__zone_symbol__Error'];
402+
const desc = Object.getOwnPropertyDescriptor(NativeError.prototype, 'stack');
403+
if (desc) {
404+
const originalSet: (value: any) => void = desc.set;
405+
// make stack readonly
406+
desc.set = null;
407+
408+
try {
409+
const error = new Error('test error');
410+
expect(error.stack).toBeTruthy();
411+
assertStackDoesNotContainZoneFrames(error);
412+
} finally {
413+
desc.set = originalSet;
414+
}
415+
}
416+
});
417+
});
269418
});

0 commit comments

Comments
 (0)