Typoskript - Fehlerklasse erweitern

79

Ich versuche, einen benutzerdefinierten Fehler mit meinem in der Konsole gedruckten Klassennamen "CustomError" anstelle von "Fehler" auszulösen, ohne Erfolg:

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

Die Ausgabe ist Uncaught Error: Lorem "foo" ipsum dolor.

Was ich erwarte : Uncaught CustomError: Lorem "foo" ipsum dolor.

Ich frage mich, ob dies nur mit TS möglich ist (ohne mit JS-Prototypen herumzuspielen).

dunkles Seelenlied
quelle

Antworten:

76

Verwenden Sie Typoskript Version 2.1 und transpilieren Sie auf ES5? Überprüfen Sie diesen Abschnitt der Seite mit den wichtigsten Änderungen auf mögliche Probleme und Problemumgehungen: https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array- und-Karte-kann-nicht-mehr-funktionieren

Das relevante Bit:

Als Empfehlung können Sie den Prototyp sofort nach Superaufrufen (...) manuell anpassen.

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

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

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

Jede Unterklasse von FooError muss den Prototyp jedoch auch manuell festlegen. Für Laufzeiten, die Object.setPrototypeOf nicht unterstützen, können Sie möglicherweise stattdessen verwenden __proto__.

Leider funktionieren diese Problemumgehungen in Internet Explorer 10 und früheren Versionen nicht. Man kann Methoden manuell vom Prototyp auf die Instanz selbst kopieren (dh FooError.prototype darauf), aber die Prototypkette selbst kann nicht repariert werden.

Vidar
quelle
2
Ja, ich transpiliere auf ES5. Ich habe versucht, den Prototyp so einzustellen, wie Sie es vorgeschlagen haben, aber leider hat es nicht funktioniert.
Darksoulsong
3
Es ist erwähnenswert, dass leider Object.setPrototypeOf(this, this.constructor.prototype)wird nicht hier arbeiten, die Klasse explizit Bezug genommen werden muss.
John Weisz
1
@JohnWeisz Wenn der von Ihnen erwähnte Fix funktioniert hat, gibt es überhaupt nichts zu reparieren. Wenn Sie den Prototyp this.constructoran dieser Stelle lesen könnten, wäre die Prototypenkette bereits intakt.
Thetrompf
3
Ab Typoskript 2.2 scheint es einen Weg , um es zu tun , ohne hartzucodieren über die genaue Art im Konstruktor: Object.setPrototypeOf(this, new.target.prototype); typescriptlang.org/docs/handbook/release-notes/...
Grabofus
62

Das Problem ist, dass die in Javascript integrierte Klasse Errordie Prototypenkette unterbricht, indem sie das zu erstellende Objekt (dh this) beim Aufrufen auf ein neues, anderes Objekt umschaltet superund das neue Objekt nicht die erwartete Prototypenkette hat, dh es ist eine Instanz von Errornicht vonCustomError .

Dieses Problem kann elegant mit 'new.target' gelöst werden, das seit Typescript 2.2 unterstützt wird. Siehe hier: 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; } 
  }
}

Die Verwendung new.targethat den Vorteil, dass Sie den Prototyp nicht wie bei einigen anderen hier vorgeschlagenen Antworten fest codieren müssen. Das hat wieder den Vorteil, dass Klassen von erbenCustomError , automatisch auch die richtige Prototypkette erhalten.

Wenn Sie den Prototyp (z. B. Object.setPrototype(this, CustomError.prototype)) fest codieren CustomErrorwürden , hätte er selbst eine funktionierende Prototypkette, aber alle Klassen, die von erben, CustomErrorwürden beschädigt, z. B. class VeryCustomError < CustomErrorwären Instanzen von a nicht instanceof VeryCustomErrorwie erwartet, sondern nurinstanceof CustomError .

Siehe auch: https://github.com/Microsoft/TypeScript/issues/13965#issuecomment-278570200

Kristian Hanekamp
quelle
Sollte this.__proto__privat oder öffentlich sein?
Lonix
Sie können es privat machen, denke ich, da Sie nicht versuchen, außerhalb des Konstruktors darauf zuzugreifen. Solange Sie es nur in TypeScript verwenden, das auf ES5 kompiliert wird, sollte dies kein Problem sein, da diese TypeScript-Funktion nur eine Funktion zur Kompilierungszeit ist. Das Feld ist in Ihrem kompilierten Code immer öffentlich zugänglich. Derzeit befindet sich der eigentliche Vorschlag für "private Klassenfelder" nur in einem experimentellen Stadium. Und die __proto__Immobilie ist auch veraltet und nur etwas standardisiert, um etwas abwärtskompatibel zu sein. Weitere Informationen zu beiden finden Sie unter MDN .
Trve
1
Oder Sie können einfach so etwas tun (this as any).__proto__ = actualProto;. Das habe ich getan. Hacky, aber ich muss keine Klassendeklarationen ändern.
Trve
16

Es funktioniert korrekt in ES2015 ( https://jsfiddle.net/x40n2gyr/ ). Das Problem besteht höchstwahrscheinlich darin, dass der TypeScript-Compiler auf ES5 transpiliert und Errornicht nur mit ES5-Funktionen korrekt in Unterklassen unterteilt werden kann. Es kann nur mit ES2015 und höheren Funktionen ( classoder, noch dunkler), korrekt in Unterklassen unterteilt werden Reflect.construct. Dies liegt daran, dass beim Aufrufen Errorals Funktion (und nicht über newoder in ES2015 superoder Reflect.construct) thiseine neue Funktion ignoriert und erstellt wird Error .

Sie müssen wahrscheinlich mit der unvollständigen Ausgabe leben, bis Sie auf ES2015 oder höher zielen können ...

TJ Crowder
quelle
2
Dies. Dies. Dies und wieder das.
Luca T
9

Ab TypeScript 2.2 kann dies über erfolgen new.target.prototype. https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html#example

class CustomError extends Error {
    constructor(message?: string) {
        super(message); // 'Error' breaks prototype chain here
        this.name = 'CustomError';
        Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain
    }
}
Grabofus
quelle
1
Dies setzt nicht die .name-Eigenschaft
retorquere
6

Ich bin vor ein paar Tagen in meinem Typoskript-Projekt auf dasselbe Problem gestoßen. Damit es funktioniert, verwende ich die Implementierung von MDN nur mit Vanilla Js. Ihr Fehler würde also ungefähr so ​​aussehen:

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');

Es scheint im SO-Code-Snippet nicht zu funktionieren, aber in der Chrome-Konsole und in meinem Typoskript-Projekt:

Geben Sie hier die Bildbeschreibung ein

Mμ.
quelle
1
Gefiel die Idee, konnte sie nicht in Typoskript implementieren
Julius Š.
"Ja wirklich?" Es funktionierte auf meinem Typoskript reagieren nativen Projekt.
Mμ.
Nun, ich wollte auch den Boilerplate-Teil abstrahieren und bin wahrscheinlich gescheitert. Ich werde es erneut ohne andere Shinanigene versuchen.
Julius Š.
strict: trueGute Idee, aber es schlägt mit dieser Nachricht fehl, wenn in tsconfig: stackoverflow.com/questions/43623461/…
André
Es funktioniert, unterbricht jedoch die Fehlerformatierung in der Browserkonsole. In der Chrome-Konsole werden Ihre CustomError-Instanzen nicht mit der üblichen besseren Formatierung angezeigt, die "echte" Fehler erhalten, sondern in einem "generischen Objekt" -Format, das schwerer zu lesen ist.
Kristian Hanekamp
0

Versuche dies...

class CustomError extends Error { 

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

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

}

throw new CustomError('foo')
Flcoder
quelle