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

Commit 82722c3

Browse files
JiaLiPassionmhevery
authored andcommitted
fix(ZoneAwareError): Error should keep prototype chain and can be called without new
Fix #546, #554, #555
1 parent 406e231 commit 82722c3

File tree

2 files changed

+97
-3
lines changed

2 files changed

+97
-3
lines changed

Diff for: lib/zone.ts

+41-3
Original file line numberDiff line numberDiff line change
@@ -1314,12 +1314,52 @@ const Zone: ZoneType = (function(global: any) {
13141314
let frameParserStrategy = null;
13151315
const stackRewrite = 'stackRewrite';
13161316

1317+
const assignAll = function(to, from) {
1318+
if (!to) {
1319+
return to;
1320+
}
1321+
1322+
if (from) {
1323+
let keys = Object.getOwnPropertyNames(from);
1324+
for (let i = 0; i < keys.length; i++) {
1325+
const key = keys[i];
1326+
// Avoid bugs when hasOwnProperty is shadowed
1327+
if (Object.prototype.hasOwnProperty.call(from, key)) {
1328+
to[key] = from[key];
1329+
}
1330+
}
1331+
1332+
// copy all properties from prototype
1333+
// in Error, property such as name/message is in Error's prototype
1334+
// but not enumerable, so we copy those properties through
1335+
// Error's prototype
1336+
const proto = Object.getPrototypeOf(from);
1337+
if (proto) {
1338+
let pKeys = Object.getOwnPropertyNames(proto);
1339+
for (let i = 0; i < pKeys.length; i++) {
1340+
const key = pKeys[i];
1341+
// skip constructor
1342+
if (key !== 'constructor') {
1343+
to[key] = from[key];
1344+
}
1345+
}
1346+
}
1347+
}
1348+
return to;
1349+
};
13171350

13181351
/**
13191352
* This is ZoneAwareError which processes the stack frame and cleans up extra frames as well as
13201353
* adds zone information to it.
13211354
*/
13221355
function ZoneAwareError() {
1356+
// make sure we have a valid this
1357+
// if this is undefined(call Error without new) or this is global
1358+
// or this is some other objects, we should force to create a
1359+
// valid ZoneAwareError by call Object.create()
1360+
if (!(this instanceof ZoneAwareError)) {
1361+
return ZoneAwareError.apply(Object.create(ZoneAwareError.prototype), arguments);
1362+
}
13231363
// Create an Error.
13241364
let error: Error = NativeError.apply(this, arguments);
13251365

@@ -1358,7 +1398,7 @@ const Zone: ZoneType = (function(global: any) {
13581398
}
13591399
error.stack = error.zoneAwareStack = frames.join('\n');
13601400
}
1361-
return error;
1401+
return assignAll(this, error);
13621402
}
13631403

13641404
// Copy the prototype so that instanceof operator works as expected
@@ -1398,8 +1438,6 @@ const Zone: ZoneType = (function(global: any) {
13981438
}
13991439
});
14001440

1401-
// Now we need to populet the `blacklistedStackFrames` as well as find the
1402-
14031441
// Now we need to populet the `blacklistedStackFrames` as well as find the
14041442
// run/runGuraded/runTask frames. This is done by creating a detect zone and then threading
14051443
// the execution through all of the above methods so that we can look at the stack trace and

Diff for: test/common/Error.spec.ts

+56
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,62 @@ describe('ZoneAwareError', () => {
1111
// and there is no point in running them.
1212
if (!Error['stackRewrite']) return;
1313

14+
it('should keep error prototype chain correctly', () => {
15+
class MyError extends Error {}
16+
const myError = new MyError();
17+
expect(myError instanceof Error).toBe(true);
18+
expect(myError instanceof MyError).toBe(true);
19+
expect(myError.stack).not.toBe(undefined);
20+
});
21+
22+
it('should instanceof error correctly', () => {
23+
let myError = Error('myError');
24+
expect(myError instanceof Error).toBe(true);
25+
let myError1 = Error.call(undefined, 'myError');
26+
expect(myError1 instanceof Error).toBe(true);
27+
let myError2 = Error.call(global, 'myError');
28+
expect(myError2 instanceof Error).toBe(true);
29+
let myError3 = Error.call({}, 'myError');
30+
expect(myError3 instanceof Error).toBe(true);
31+
let myError4 = Error.call({test: 'test'}, 'myError');
32+
expect(myError4 instanceof Error).toBe(true);
33+
});
34+
35+
it('should return error itself from constructor', () => {
36+
class MyError1 extends Error {
37+
constructor() {
38+
const err: any = super('MyError1');
39+
this.message = err.message;
40+
}
41+
}
42+
let myError1 = new MyError1();
43+
expect(myError1.message).toEqual('MyError1');
44+
expect(myError1.name).toEqual('Error');
45+
});
46+
47+
it('should return error by calling error directly', () => {
48+
let myError = Error('myError');
49+
expect(myError.message).toEqual('myError');
50+
let myError1 = Error.call(undefined, 'myError');
51+
expect(myError1.message).toEqual('myError');
52+
let myError2 = Error.call(global, 'myError');
53+
expect(myError2.message).toEqual('myError');
54+
let myError3 = Error.call({}, 'myError');
55+
expect(myError3.message).toEqual('myError');
56+
});
57+
58+
it('should have browser specified property', () => {
59+
let myError = new Error('myError');
60+
if (Object.prototype.hasOwnProperty.call(Error.prototype, 'description')) {
61+
// in IE, error has description property
62+
expect((<any>myError).description).toEqual('myError');
63+
}
64+
if (Object.prototype.hasOwnProperty.call(Error.prototype, 'fileName')) {
65+
// in firefox, error has fileName property
66+
expect((<any>myError).fileName).toContain('zone');
67+
}
68+
});
69+
1470
it('should show zone names in stack frames and remove extra frames', () => {
1571
const rootZone = getRootZone();
1672
const innerZone = rootZone.fork({name: 'InnerZone'});

0 commit comments

Comments
 (0)