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

Commit

Permalink
fix(ZoneAwareError): Error should keep prototype chain and can be cal…
Browse files Browse the repository at this point in the history
…led without new

Fix #546, #554, #555
  • Loading branch information
JiaLiPassion authored and mhevery committed Jan 6, 2017
1 parent 406e231 commit 82722c3
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 3 deletions.
44 changes: 41 additions & 3 deletions lib/zone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1314,12 +1314,52 @@ const Zone: ZoneType = (function(global: any) {
let frameParserStrategy = null;
const stackRewrite = 'stackRewrite';

const assignAll = function(to, from) {
if (!to) {
return to;
}

if (from) {
let keys = Object.getOwnPropertyNames(from);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
// Avoid bugs when hasOwnProperty is shadowed
if (Object.prototype.hasOwnProperty.call(from, key)) {
to[key] = from[key];
}
}

// copy all properties from prototype
// in Error, property such as name/message is in Error's prototype
// but not enumerable, so we copy those properties through
// Error's prototype
const proto = Object.getPrototypeOf(from);
if (proto) {
let pKeys = Object.getOwnPropertyNames(proto);
for (let i = 0; i < pKeys.length; i++) {
const key = pKeys[i];
// skip constructor
if (key !== 'constructor') {
to[key] = from[key];
}
}
}
}
return to;
};

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

Expand Down Expand Up @@ -1358,7 +1398,7 @@ const Zone: ZoneType = (function(global: any) {
}
error.stack = error.zoneAwareStack = frames.join('\n');
}
return error;
return assignAll(this, error);
}

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

// Now we need to populet the `blacklistedStackFrames` as well as find the

// Now we need to populet the `blacklistedStackFrames` as well as find the
// run/runGuraded/runTask frames. This is done by creating a detect zone and then threading
// the execution through all of the above methods so that we can look at the stack trace and
Expand Down
56 changes: 56 additions & 0 deletions test/common/Error.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,62 @@ describe('ZoneAwareError', () => {
// and there is no point in running them.
if (!Error['stackRewrite']) return;

it('should keep error prototype chain correctly', () => {
class MyError extends Error {}
const myError = new MyError();
expect(myError instanceof Error).toBe(true);
expect(myError instanceof MyError).toBe(true);
expect(myError.stack).not.toBe(undefined);
});

it('should instanceof error correctly', () => {
let myError = Error('myError');
expect(myError instanceof Error).toBe(true);
let myError1 = Error.call(undefined, 'myError');
expect(myError1 instanceof Error).toBe(true);
let myError2 = Error.call(global, 'myError');
expect(myError2 instanceof Error).toBe(true);
let myError3 = Error.call({}, 'myError');
expect(myError3 instanceof Error).toBe(true);
let myError4 = Error.call({test: 'test'}, 'myError');
expect(myError4 instanceof Error).toBe(true);
});

it('should return error itself from constructor', () => {
class MyError1 extends Error {
constructor() {
const err: any = super('MyError1');
this.message = err.message;
}
}
let myError1 = new MyError1();
expect(myError1.message).toEqual('MyError1');
expect(myError1.name).toEqual('Error');
});

it('should return error by calling error directly', () => {
let myError = Error('myError');
expect(myError.message).toEqual('myError');
let myError1 = Error.call(undefined, 'myError');
expect(myError1.message).toEqual('myError');
let myError2 = Error.call(global, 'myError');
expect(myError2.message).toEqual('myError');
let myError3 = Error.call({}, 'myError');
expect(myError3.message).toEqual('myError');
});

it('should have browser specified property', () => {
let myError = new Error('myError');
if (Object.prototype.hasOwnProperty.call(Error.prototype, 'description')) {
// in IE, error has description property
expect((<any>myError).description).toEqual('myError');
}
if (Object.prototype.hasOwnProperty.call(Error.prototype, 'fileName')) {
// in firefox, error has fileName property
expect((<any>myError).fileName).toContain('zone');
}
});

it('should show zone names in stack frames and remove extra frames', () => {
const rootZone = getRootZone();
const innerZone = rootZone.fork({name: 'InnerZone'});
Expand Down

0 comments on commit 82722c3

Please sign in to comment.