Typescript-확장 오류 클래스


81

"Error"대신 콘솔에 "CustomError"클래스 이름이 인쇄 된 사용자 지정 오류를 던지려고하는데 성공하지 못했습니다.

class CustomError extends Error { 
    constructor(message: string) {
      super(`Lorem "${message}" ipsum dolor.`);
      this.name = 'CustomError';
    }
}
throw new CustomError('foo'); 

출력은 Uncaught Error: Lorem "foo" ipsum dolor.

내가 기대하는 것 : Uncaught CustomError: Lorem "foo" ipsum dolor.

TS 만 사용할 수 있는지 궁금합니다 (JS 프로토 타입을 엉망으로 만들지 않고)?

답변:


78

Typescript 버전 2.1을 사용하고 있고 ES5로 트랜스 파일하고 있습니까? 주요 변경 사항 페이지의이 섹션에서 가능한 문제 및 해결 방법을 확인하십시오. https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array- 더 이상 작동하지 않을 수도 있습니다.

관련 비트 :

권장 사항으로 super (...) 호출 직후 프로토 타입을 수동으로 조정할 수 있습니다.

class FooError extends Error {
    constructor(m: string) {
        super(m);

        // Set the prototype explicitly.
        Object.setPrototypeOf(this, FooError.prototype);
    }

    sayHello() {
        return "hello " + this.message;
    }
}

그러나 FooError의 모든 하위 클래스는 프로토 타입도 수동으로 설정해야합니다. Object.setPrototypeOf를 지원하지 않는 런타임의 경우 대신 __proto__.

안타깝게도 이러한 해결 방법은 Internet Explorer 10 이하에서는 작동하지 않습니다. 프로토 타입에서 인스턴스 자체로 메서드를 수동으로 복사 할 수 있지만 (예 : FooError.prototype). 프로토 타입 체인 자체는 수정할 수 없습니다.


2
예, ES5로 변환하고 있습니다. 제안한대로 프로토 타입을 설정하려고했지만 안타깝게도 작동하지 않았습니다.
darksoulsong dec.

3
그것은 불행하게도 있음을 주목할 필요가있다 Object.setPrototypeOf(this, this.constructor.prototype)할 것이다 되지 여기서 일, 클래스가 명시 적으로 참조 할 수있다.
John Weisz

1
@JohnWeisz 당신이 언급하고있는 수정 사항이 효과가 있었다면, 처음부터 수정할 것이 없을 것입니다 this.constructor.이 시점에서 프로토 타입을 읽을 수 있다면 프로토 타입 체인은 이미 손상되지 않았을 것입니다.
thetrompf

3
TypeScript 2.2부터는 다음을 통해 생성자에서 정확한 유형을 하드 코딩하지 않고이를 수행하는 방법이있는 것 같습니다. Object.setPrototypeOf(this, new.target.prototype); typescriptlang.org/docs/handbook/release-notes/…
Grabofus

65

문제는 Javascript의 내장 클래스 Errorthis호출 할 때 생성 할 객체 (예 :) 를 새롭고 다른 객체 로 전환하여 프로토 타입 체인을 끊고 super새 객체에 예상되는 프로토 타입 체인이없는 경우, 즉 인스턴스라는 것입니다. 의 Error하지 않는 CustomError.

이 문제는 Typescript 2.2부터 지원되는 'new.target'을 사용하여 우아하게 해결할 수 있습니다. https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html 참조

class CustomError extends Error {
  constructor(message?: string) {
    // 'Error' breaks prototype chain here
    super(message); 

    // restore prototype chain   
    const actualProto = new.target.prototype;

    if (Object.setPrototypeOf) { Object.setPrototypeOf(this, actualProto); } 
    else { this.__proto__ = actualProto; } 
  }
}

사용하면 new.target여기에 제안 된 다른 답변처럼 프로토 타입을 하드 코딩 할 필요가 없다는 장점이 있습니다. 이는 상속 된 클래스 CustomError가 자동으로 올바른 프로토 타입 체인을 얻는다는 장점이 있습니다 .

프로토 타입을 하드 코딩하면 (예 :) Object.setPrototype(this, CustomError.prototype), CustomError자체적으로 작동하는 프로토 타입 체인을 가지지 만에서 상속하는 모든 클래스 CustomError가 손상됩니다. 예를 들어 a의 인스턴스가 예상대로 class VeryCustomError < CustomError되지 않고 .instanceof VeryCustomErrorinstanceof CustomError

참조 : https://github.com/Microsoft/TypeScript/issues/13965#issuecomment-278570200


this.__proto__비공개 또는 공개 여야합니까 ?
lonix

생성자 외부에서 액세스하지 않기 때문에 비공개로 설정할 수 있습니다. 따라서 ES5로 컴파일되는 TypeScript에서만 사용하는 한이 TypeScript 기능은 컴파일 타임 기능 일 뿐이므로 문제가되지 않습니다. 이 필드는 컴파일 된 코드에서 항상 공개적으로 액세스 할 수 있습니다. 현재 실제 "개인 클래스 필드"제안은 실험 단계에 불과합니다. 또한이 __proto__속성은 더 이상 사용되지 않으며 이전 버전과 호환되도록 다소 표준화되었습니다. 둘 다에 대한 자세한 내용은 MDN 을 참조하십시오 .
Trve

1
또는 간단히 (this as any).__proto__ = actualProto;. 이것이 내가 한 일입니다. Hacky이지만 클래스 선언을 변경할 필요가 없습니다.
Trve

18

ES2015 ( https://jsfiddle.net/x40n2gyr/ ) 에서 올바르게 작동합니다 . 문제는 TypeScript 컴파일러가 ES5로 트랜스 파일되고 ErrorES5 기능 만 사용하여 올바르게 하위 클래스 화 될 수 없다는 것입니다. ES2015 이상 기능 ( class또는 더 모호하게 Reflect.construct)을 사용해서 만 올바르게 서브 클래 싱 할 수 있습니다 . 이는 Error함수로 호출 할 때 ( new또는을 통해 ES2015 super또는에서 Reflect.construct) 함수로 호출 할 때 무시 this하고 새로운 Error .

ES2015 이상을 목표로 삼을 때까지 불완전한 결과물을 가지고 살아야 할 것입니다.


2
이. 이. 이것과 또 이것.
Luca T

여기에 다른 답변에서 언급했듯이 이미 해결 방법이 있으므로 추가 복잡성을 수용하고 프로토 타입 체인 업데이트를 구현하는 것이 좋습니다.
JoshuaTree


6

며칠 전에 typescript 프로젝트에서 동일한 문제가 발생했습니다. 작동하도록하려면 바닐라 js 만 사용하는 MDN 의 구현을 사용합니다. 따라서 오류는 다음과 같습니다.

function CustomError(message) {
  this.name = 'CustomError';
  this.message = message || 'Default Message';
  this.stack = (new Error()).stack;
}
CustomError.prototype = Object.create(Error.prototype);
CustomError.prototype.constructor = CustomError;

throw new CustomError('foo');

SO 코드 스 니펫에서는 작동하지 않는 것 같지만 크롬 콘솔과 내 typescript 프로젝트에서는 작동합니다.

여기에 이미지 설명 입력


1
아이디어가 마음에 들었지만 타이프 스크립트로 구현하지 못했습니다
Julius Š.

정말? 내 typescript 반응 네이티브 프로젝트에서 작동했습니다.
Mμ.

글쎄, 나는 또한 상용구 부분을 추상화하고 싶었고 아마도 그렇게하지 못했을 것입니다. 다른 shinanigen없이 다시 시도 할 것입니다.
Julius Š.

좋은 생각은하지만이 메시지와 함께 실패 할 때 strict: truetsconfig에서 : stackoverflow.com/questions/43623461/...
앙드레

작동하지만 브라우저 콘솔의 오류 형식화가 중단됩니다. Chrome 콘솔에서 CustomError 인스턴스는 "진정한"오류가 가져 오는 더 좋은 형식으로 표시되지 않지만 읽기가 더 어려운 "일반 개체"형식으로 표시됩니다.
Kristian Hanekamp

2

나는 말 그대로 SO에 글을 올리지 않았지만 우리 팀은 TypeScript 프로젝트를 진행하고 있으며 우리는 es5를 대상으로하는 동시에 많은 사용자 정의 오류 클래스를 만들어야했습니다. 모든 단일 오류 클래스에서 제안 된 수정 사항을 수행하는 것은 엄청나게 지루했을 것입니다. 그러나 우리는 메인 커스텀 에러 클래스를 생성하고 나머지 에러를 extend그 클래스에 포함 함으로써 모든 후속 에러 클래스에 다운 스트림 영향을 미칠 수 있음을 발견했습니다 . 기본 오류 클래스 내에서 프로토 타입 업데이트의 다운 스트림 효과를 얻기 위해 다음을 수행했습니다.

class MainErrorClass extends Error {
  constructor() {
    super()
    Object.setPrototypeOf(this, new.target.prototype)
  }
}

class SomeNewError extends MainErrorClass {} 

...

사용 new.target.prototype 은 각 생성자를 업데이트 할 필요없이 상속되는 모든 오류 클래스를 업데이트하는 데 핵심이었습니다.

이것이 앞으로 다른 사람의 두통을 덜어주기를 바라는 것뿐입니다!


1

이 시도...

class CustomError extends Error { 

  constructor(message: string) {
    super(`Lorem "${message}" ipsum dolor.`)
  }

  get name() { return this.constructor.name }

}

throw new CustomError('foo')

그러나 이것은 이미 존재하는 기능을 사용하지 않는 것으로 보이며 말하자면 휠을 다시 구현해야합니다. 아니면 내가 착각 했나?
JoshuaTree

@JoshuaTree 솔루션이 사용하지 않는 기존 기능은 무엇입니까? 어떤 바퀴가 다시 구현되고 있습니까? 당신은 착각하지 않을 수도 있지만 당신의 주장은 좀 더 구체적 일 수 있습니다.
flcoder

미안 해요, 제 실수입니다. typescript src를 확인한 결과 getter 및 setter가있는 실제 개체가 아닌 인터페이스임을 확인했습니다. 구현은 기존 기능을 재정의하지 않습니다.
JoshuaTree
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.