Typescript: Vorschlag: `throws`-Klausel und getippte catch-Klausel

Erstellt am 29. Dez. 2016  ·  135Kommentare  ·  Quelle: microsoft/TypeScript

Das Typescript-Typsystem ist in den meisten Fällen hilfreich, kann jedoch nicht zur Behandlung von Ausnahmen verwendet werden.
Beispielsweise:

function fn(num: number): void {
    if (num === 0) {
        throw "error: can't deal with 0";
    }
}

Das Problem hier ist zweifach (ohne den Code durchzusehen):

  1. Wenn Sie diese Funktion verwenden, können Sie nicht wissen, ob sie einen Fehler auslösen könnte
  2. Es ist nicht klar, um welche Art(en) des Fehlers es sich handelt

In vielen Szenarien sind dies nicht wirklich ein Problem, aber zu wissen, ob eine Funktion/Methode eine Ausnahme auslösen könnte, kann in verschiedenen Szenarien sehr nützlich sein, insbesondere wenn verschiedene Bibliotheken verwendet werden.

Durch die Einführung einer (optionalen) geprüften Ausnahme kann das Typsystem für die Ausnahmebehandlung verwendet werden.
Ich weiß, dass geprüfte Ausnahmen nicht vereinbart sind (z. B. Anders Hejlsberg ), aber wenn Sie es optional machen (und vielleicht gefolgert? später mehr), dann bietet es nur die Möglichkeit, weitere Informationen über den Code hinzuzufügen, die Entwicklern, Tools und helfen können Dokumentation.
Es ermöglicht auch eine bessere Nutzung sinnvoller benutzerdefinierter Fehler für große Projekte.

Da alle Javascript-Laufzeitfehler vom Typ Error (oder Erweiterungstypen wie TypeError ) sind, ist der tatsächliche Typ für eine Funktion immer type | Error .

Die Grammatik ist einfach, eine Funktionsdefinition kann mit einer throws-Klausel enden, gefolgt von einem Typ:

function fn() throws string { ... }
function fn(...) throws string | number { ... }

class MyError extends Error { ... }
function fn(...): Promise<string> throws MyError { ... }

Beim Abfangen der Ausnahmen ist die Syntax dieselbe, mit der Möglichkeit, den/die Fehlertyp(en) zu deklarieren:
catch(e: string | Error) { ... }

Beispiele:

function fn(num: number): void throws string {
    if (num === 0) {
        throw "error: can't deal with 0";
    }
}

Hier ist klar, dass die Funktion einen Fehler werfen kann und dass der Fehler ein String sein wird, sodass der Entwickler (und der Compiler/die IDE) sich dessen bewusst ist und besser damit umgehen kann, wenn er diese Methode aufruft.
So:

fn(0);

// or
try {
    fn(0); 
} catch (e: string) { ... }

Kompiliert ohne Fehler, aber:

try {
    fn(0); 
} catch (e: number) { ... }

Kann nicht kompiliert werden, weil number nicht string ist.

Kontrollfluss und Fehlertypinferenz

try {
    fn(0);
} catch(e) {
    if (typeof e === "string") {
        console.log(e.length);
    } else if (e instanceof Error) {
        console.log(e.message);
    } else if (typeof e === "string") {
        console.log(e * 3); // error: Unreachable code detected
    }

    console.log(e * 3); // error: The left-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type
}
function fn(num: number): void {
    if (num === 0) {
        throw "error: can't deal with 0";
    }
}

Wirft string .

function fn2(num: number) {
    if (num < 0) {
        throw new MyError("can only deal with positives");
    }

    fn(num);
}

Wirft MyError | string .
Jedoch:

function fn2(num: number) {
    if (num < 0) {
        throw new MyError("can only deal with positives");
    }

    try {
        fn(num);
    } catch(e) {
        if (typeof e === "string") {
           throw new MyError(e);
       } 
    }
}

Wirft nur MyError .

Awaiting More Feedback Suggestion

Hilfreichster Kommentar

@aleksey-bykov

Sie schlagen vor, throw überhaupt nicht in meinem Code zu verwenden und stattdessen die Ergebnisse einzuschließen (in Funktionen, die Fehler verursachen könnten).
Dieser Ansatz hat einige Nachteile:

  • Durch dieses Wrapping wird mehr Code erstellt
  • Es erfordert, dass alle Ketten aufgerufener Funktionen diesen umschlossenen Wert (oder Fehler) zurückgeben, oder alternativ kann die Funktion, die Tried<> erhält, den Fehler nicht ignorieren.
  • Es handelt sich nicht um Standardbibliotheken von Drittanbietern, und die nativen js werfen Fehler aus

Das Hinzufügen throws ermöglicht es Entwicklern, Fehler aus ihrem Code, 3rd-Bibliotheken und nativen js zu behandeln.
Da der Vorschlag auch Fehlerrückschlüsse fordert, können alle generierten Definitionsdateien die Klausel throws enthalten.
Es ist sehr praktisch zu wissen, welche Fehler eine Funktion direkt aus der Definitionsdatei werfen könnte, anstatt den aktuellen Status zu erfahren, wo Sie zur Dokumentation gehen müssen, um beispielsweise zu wissen, welchen Fehler JSON.parse ausgeben könnte, den ich gehen muss auf die MDN-Seite und lesen Sie das:

Löst eine SyntaxError -Ausnahme aus, wenn die zu parsende Zeichenfolge kein gültiges JSON ist

Und das ist der gute Fall, wenn der Fehler dokumentiert ist.

Alle 135 Kommentare

Nur zur Verdeutlichung - eine der Ideen hier ist nicht, Benutzer zu zwingen, die Ausnahme abzufangen, sondern besser auf den Typ einer Catch-Klausel-Variablen zu schließen?

@DanielRosenwasser
Ja, Benutzer werden nicht gezwungen, Ausnahmen abzufangen, daher ist dies für den Compiler in Ordnung (zur Laufzeit wird der Fehler natürlich ausgegeben):

function fn() {
    throw "error";
}

fn();

// and
try {
    fn();
} finally {
    // do something here
}

Aber es wird Entwicklern die Möglichkeit geben, auszudrücken, welche Ausnahmen ausgelöst werden können (wäre toll, das zu haben, wenn andere .d.ts -Dateien aus Bibliotheken verwendet werden) und dann den Compilertyp die Ausnahmetypen in der catch-Klausel schützen zu lassen.

Wie unterscheidet sich ein gecheckter Wurf von Tried<Result, Error> ?

type Tried<Result, Error> = Success<Result> | Failure<Error>;
interface Success<Result> { kind: 'result', result: Result } 
interface Failure<Error> { kind: 'failure', error: Error }
function isSuccess(tried: Tried<Result, Error>): tried is Success<Result> {
   return tried.kind === 'result';
}
function mightFail(): Tried<number, string> {
}
const tried = mightFail();
if (isSuccess(tried)) {
    console.log(tried.success);
}  else {
    console.error(tried.error);
}

anstatt

try {
    const result: Result = mightFail();
    console.log(success);
} catch (error: Error) {
    console.error(error);
}

@aleksey-bykov

Sie schlagen vor, throw überhaupt nicht in meinem Code zu verwenden und stattdessen die Ergebnisse einzuschließen (in Funktionen, die Fehler verursachen könnten).
Dieser Ansatz hat einige Nachteile:

  • Durch dieses Wrapping wird mehr Code erstellt
  • Es erfordert, dass alle Ketten aufgerufener Funktionen diesen umschlossenen Wert (oder Fehler) zurückgeben, oder alternativ kann die Funktion, die Tried<> erhält, den Fehler nicht ignorieren.
  • Es handelt sich nicht um Standardbibliotheken von Drittanbietern, und die nativen js werfen Fehler aus

Das Hinzufügen throws ermöglicht es Entwicklern, Fehler aus ihrem Code, 3rd-Bibliotheken und nativen js zu behandeln.
Da der Vorschlag auch Fehlerrückschlüsse fordert, können alle generierten Definitionsdateien die Klausel throws enthalten.
Es ist sehr praktisch zu wissen, welche Fehler eine Funktion direkt aus der Definitionsdatei werfen könnte, anstatt den aktuellen Status zu erfahren, wo Sie zur Dokumentation gehen müssen, um beispielsweise zu wissen, welchen Fehler JSON.parse ausgeben könnte, den ich gehen muss auf die MDN-Seite und lesen Sie das:

Löst eine SyntaxError -Ausnahme aus, wenn die zu parsende Zeichenfolge kein gültiges JSON ist

Und das ist der gute Fall, wenn der Fehler dokumentiert ist.

Und das ist der gute Fall, wenn der Fehler dokumentiert ist.

Gibt es in Javascript eine zuverlässige Möglichkeit, SyntaxError von Error zu unterscheiden?

  • Ja, es ist mehr Code, aber da eine schlechte Situation in einem Objekt dargestellt wird, kann es wie jeder andere Wert weitergereicht werden, um verarbeitet, verworfen, gespeichert oder in ein gültiges Ergebnis umgewandelt zu werden

  • Sie können versucht ignorieren, indem Sie auch versucht zurückgeben, versucht kann als eine Monade angesehen werden, suchen Sie nach monadischen Berechnungen

    function mightFail(): Tried<number, string> {
    }
    function mightFailToo(): Tried<number, string> {
        const tried = mightFail();
        if (isSuccess(tried))  { 
             return successFrom(tried.result * 2);
        } else {
             return tried;
        }
    }
    
  • Es ist Standard genug für Ihren Code, wenn es um Libs von Drittanbietern geht, die eine Ausnahme auslösen, bedeutet dies im Allgemeinen ein Gameover für Sie, da es nahezu unmöglich ist, eine Ausnahme zuverlässig wiederherzustellen. Der Grund dafür ist, dass sie von überall innerhalb des Codes ausgelöst werden kann es an einer willkürlichen Position beenden und seinen internen Zustand unvollständig oder beschädigt belassen

  • Es gibt keine Unterstützung für geprüfte Ausnahmen von der JavaScript-Laufzeit, und ich fürchte, es kann nicht allein in Typoskript implementiert werden

Abgesehen davon, dass das Codieren einer Ausnahme als Sonderergebnisfall eine sehr gängige Praxis in der FP-Welt ist

während ein mögliches Ergebnis in 2 Teile aufgeteilt wird:

  • eine von der Rücksendung gelieferte Erklärung und
  • ein anderer wird per Wurf geliefert

sieht aus wie eine erfundene Schwierigkeit

Meiner Meinung nach ist throw gut, um schnell und laut zu scheitern, wenn man nichts dagegen tun kann, explizit codierte Ergebnisse sind gut für alles, was eine schlechte, aber erwartete Situation impliziert, von der man sich erholen kann

Erwägen:

// throw/catch
declare function doThis(): number throws string;
declare function doThat(): number throws string;
function doSomething(): number throws string {
    let oneResult: number | undefined = undefined;
    try {
        oneResult = doThis();
    } catch (e) {
        throw e;
    }

    let anotherResult: number | undefined = undefined;
    try {
        anotherResult = doThat();
    } catch (e) {
        throw e;
    }
    return oneResult + anotherResult;
}

// explicit results
declare function doThis(): Tried<number, string>;
declare function doThat(): Tried<number, string>;
function withBothTried<T, E, R>(one: Tried<T, E>, another: Tried<T, E>, haveBoth: (one: T, another: T) => R): Tried<T, R> {
    return isSuccess(one)
        ? isSuccess(another)
            ? successFrom(haveBoth(one.result, another.result))
            : another
        : one;
}
function add(one: number, another: number) { return one + another; }
function doSomething(): Tried<number, string> {
    return withBothTried(
        doThis(),
        doThat(),
        add
    );
}

@aleksey-bykov

Mein Punkt mit JSON.parse könnte SyntaxError werfen ist, dass ich die Funktion in den Dokumenten nachschlagen muss, nur um zu wissen, dass sie werfen könnte, und es wäre einfacher, das in .d.ts zu sehen
Und ja, Sie können wissen, dass es SyntaxError ist, wenn Sie instanceof verwenden.

Sie können die gleiche schlechte Situation darstellen, indem Sie einen Fehler werfen.
Sie können Ihre eigene Fehlerklasse erstellen, die Error erweitert, und alle relevanten Daten, die Sie benötigen, darin ablegen.
Sie erhalten dasselbe mit weniger Code.

Manchmal haben Sie eine lange Kette von Funktionsaufrufen und möchten vielleicht einige der Fehler auf verschiedenen Ebenen der Kette behandeln.
Es wird ziemlich nervig sein, immer verpackte Ergebnisse (Monaden) zu verwenden.
Ganz zu schweigen davon, dass andere Bibliotheken und native Fehler sowieso ausgelöst werden könnten, sodass Sie möglicherweise sowohl Monaden als auch try/catch verwenden.

Ich stimme Ihnen nicht zu, in vielen Fällen können Sie sich von geworfenen Fehlern erholen, und wenn die Sprache es Ihnen ermöglicht, es besser auszudrücken, wird es einfacher sein, dies zu tun.

Wie bei vielen Dingen in Typoskript ist die fehlende Unterstützung der Funktion in Javascript kein Problem.
Dies:

try {
    mightFail();
} catch (e: MyError | string) {
    if (e instanceof MyError) { ... }
    else if (typeof e === "string") { ... }
    else {}
}

Funktioniert wie erwartet in Javascript, nur ohne die Typanmerkung.

Die Verwendung throw reicht aus, um auszudrücken, was Sie sagen: Wenn die Operation erfolgreich war, geben Sie den Wert zurück, andernfalls geben Sie einen Fehler aus.
Der Benutzer dieser Funktion entscheidet dann, ob er sich mit den möglichen Fehlern befassen oder sie ignorieren möchte.
Sie können nur mit Fehlern umgehen, die Sie selbst ausgelöst haben, und diejenigen ignorieren, die beispielsweise von Drittanbietern stammen.

Wenn wir über Browser sprechen, ist instanceof nur gut für Dinge, die aus demselben Fenster/Dokument stammen, versuchen Sie es:

var child = window.open('about:blank');
console.log(child.Error === window.Error);

also wenn du es tust:

try { child.doSomething(); } catch (e) { if (e instanceof SyntaxError) { } }

du wirst es nicht fangen

Ein weiteres Problem mit Ausnahmen ist, dass sie von weit außerhalb dessen, wo Sie sie erwarten, in Ihren Code gelangen könnten

try {
   doSomething(); // <-- uses 3rd party library that by coincidence throws SyntaxError too, but you don' t know it 
} catch (e) {}

außerdem ist instanceof anfällig für Prototyp-Vererbung, daher müssen Sie besonders vorsichtig sein, um immer mit dem endgültigen Vorfahren zu vergleichen

class StandardError {}
class CustomError extends StandardError {
}
function doSomething() { throw new CustomError(); }
function oldCode() {
   try {
      doSomething();
   } catch (e) {
      if (e instanceof StandardError) {
          // problem
      }
   }
}

@aleksey-bykov Das explizite Einfädeln von Fehlern, wie Sie es in monadischen Strukturen vorschlagen, ist eine ziemlich schwierige und entmutigende Aufgabe. Es kostet viel Mühe, macht den Code schwer verständlich und erfordert Sprachunterstützung / typgesteuertes Emit, um am Rande des Erträglichen zu sein. Dies ist ein Kommentar von jemandem, der sich viel Mühe gibt, Haskell und FP als Ganzes bekannt zu machen.

Es ist eine funktionierende Alternative, besonders für Enthusiasten (mich eingeschlossen), aber ich denke nicht, dass es eine praktikable Option für das größere Publikum ist.

Eigentlich ist meine Hauptsorge hier, dass die Leute anfangen werden, Fehler zu unterteilen. Ich denke, das ist ein schreckliches Muster. Allgemeiner gesagt, wird alles, was die Verwendung des Operators instanceof fördert, nur zusätzliche Verwirrung in Bezug auf Klassen stiften.

Dies ist ein Kommentar von jemandem, der sich viel Mühe gibt, Haskell und FP als Ganzes bekannt zu machen.

Ich denke wirklich, dass dies dem Publikum stärker vermittelt werden sollte, erst wenn es verdaut und nach mehr gefragt ist, können wir eine bessere FP-Unterstützung in der Sprache haben

und es ist nicht so entmutigend, wie Sie denken, vorausgesetzt, alle Kombinatoren sind bereits geschrieben, verwenden Sie sie einfach, um einen Datenfluss zu erstellen, wie wir es in unserem Projekt tun, aber ich stimme zu, dass TS es besser hätte unterstützen können: #2319

Monad-Transformatoren sind ein echtes PITA. Sie müssen ziemlich oft heben, hochziehen und selektiv laufen. Das Endergebnis ist kaum verständlicher Code und eine viel höhere Eintrittsbarriere als nötig. Alle Kombinatoren und Hebefunktionen (die das obligatorische Boxen/Unboxen ermöglichen) sind nur Geräusche, die Sie vom eigentlichen Problem ablenken. Ich glaube, dass es eine gute Sache ist, explizit über Zustand, Effekte usw. zu sprechen, aber ich glaube nicht, dass wir noch eine geeignete Umhüllung / Abstraktion gefunden haben. Bis wir es finden, scheint die Unterstützung traditioneller Programmiermuster der richtige Weg zu sein, ohne in der Zwischenzeit anzuhalten, um zu experimentieren und zu erforschen.

PS: Ich denke, wir brauchen mehr als benutzerdefinierte Operatoren. Höherkindige Typen und eine Art Typklassen sind auch für eine praktische monadische Bibliothek unerlässlich. Unter ihnen würde ich HKT an erster Stelle bewerten und Typenklassen an zweiter Stelle. Nach alledem glaube ich, dass TypeScript nicht die Sprache ist, um solche Konzepte zu praktizieren. Herumspielen - ja, aber seine Philosophie und Wurzeln sind für eine richtige nahtlose Integration grundlegend weit entfernt.

Zurück zur OP-Frage – instanceof ist ein gefährlicher Operator. Explizite Ausnahmen sind jedoch nicht auf Error beschränkt. Sie können auch Ihre eigenen ADTs oder benutzerdefinierten POJO-Fehler auslösen. Das vorgeschlagene Feature kann durchaus nützlich sein und natürlich auch ziemlich heftig missbraucht werden. Auf jeden Fall macht es Funktionen transparenter, was zweifellos eine gute Sache ist. Insgesamt bin ich 50/50 davon :)

@aleksey-bykov

Entwickler sollten sich der verschiedenen js-Probleme bewusst sein, die Sie beschrieben haben, schließlich führt das Hinzufügen throws zu Typoskript nichts Neues zu js ein, es gibt Typoskript nur als Sprache die Möglichkeit, ein vorhandenes js-Verhalten auszudrücken.

Die Tatsache, dass Bibliotheken von Drittanbietern Fehler werfen können, ist genau mein Punkt.
Wenn ihre Definitionsdateien das enthalten würden, hätte ich eine Möglichkeit, es zu wissen.

@aluanhaddad
Warum ist es ein schreckliches Muster, Error zu verlängern?

@gcneu
Was instanceof , das war nur ein Beispiel, kann ich immer normale Objekte werfen, die unterschiedliche Typen haben, und dann Typwächter verwenden, um zwischen ihnen zu unterscheiden.
Es ist Sache des Entwicklers, zu entscheiden, welche Art von Fehlern er werfen möchte, und das ist wahrscheinlich bereits der Fall, aber derzeit gibt es keine Möglichkeit, dies auszudrücken, was dieser Vorschlag lösen möchte.

@nitzantomer Das Unterklassen von nativen Klassen ( Error , Array , RegExp usw.) wurde in älteren ECMAScript-Versionen (vor ES6) nicht unterstützt. Der Down-Level-Ausstoß für diese Klassen führt zu unerwarteten Ergebnissen (es werden alle Anstrengungen unternommen, aber das ist alles, was man tun kann) und ist der Grund für zahlreiche Probleme, die täglich protokolliert werden. Als Faustregel gilt: Machen Sie keine Unterklassen von Natives, es sei denn, Sie zielen auf neuere ECMAScript-Versionen ab und wissen wirklich, was Sie tun.

@gcneu
Oh, das ist mir sehr wohl bewusst, da ich mehr als ein paar Stunden damit verbracht habe, herauszufinden, was schief gelaufen ist.
Aber mit der Möglichkeit, dies jetzt zu tun, sollte es keinen Grund geben, dies nicht zu tun (wenn auf es6 abzielt).

Auf jeden Fall geht dieser Vorschlag nicht davon aus, dass der Benutzer die Fehlerklasse unterordnet, es war nur ein Beispiel.

@nitzantomer Ich behaupte nicht, dass der Vorschlag auf Error beschränkt ist. Ich habe gerade erklärt, warum es ein schlechtes Muster ist, es zu unterteilen. In meinem Beitrag habe ich tatsächlich die Haltung verteidigt, dass auch benutzerdefinierte Objekte oder diskriminierte Gewerkschaften verwendet werden können.

instanceof ist gefährlich und wird als Anti-Pattern angesehen, selbst wenn Sie die Besonderheiten von JavaScript außer Acht lassen - z. B. Vorsicht vor instanceof-Operator . Der Grund dafür ist, dass der Compiler Sie nicht vor Fehlern schützen kann, die durch neue Unterklassen eingeführt werden. Die Logik, die instanceof verwendet, ist anfällig und folgt nicht dem Open/Closed -Prinzip, da sie nur eine Handvoll Optionen erwartet. Selbst wenn ein Wildcard-Fall hinzugefügt wird, verursachen neue Ableitungen wahrscheinlich immer noch Fehler, da sie die zum Zeitpunkt des Schreibens getroffenen Annahmen zunichte machen können.

Für die Fälle, in denen Sie zwischen bekannten Alternativen unterscheiden möchten, verfügt TypeScript über Tagged Unions (auch Discriminated Unions oder algebraische Datentypen genannt). Der Compiler stellt sicher, dass alle Fälle behandelt werden, was Ihnen gute Garantien gibt. Der Nachteil ist, dass Sie, wenn Sie dem Typ einen neuen Eintrag hinzufügen möchten, den gesamten Code durchlaufen müssen, der ihn unterscheidet, und die neu hinzugefügte Option handhaben müssen. Der Vorteil ist, dass ein solcher Code höchstwahrscheinlich beschädigt worden wäre, aber zur Laufzeit fehlgeschlagen wäre.

Ich habe diesen Vorschlag nur noch einmal überlegt und bin dagegen geworden. Der Grund dafür ist, dass, wenn throws -Deklarationen auf Signaturen vorhanden waren, aber nicht erzwungen wurden, sie bereits durch Dokumentationskommentare behandelt werden können. Im Falle einer Durchsetzung teile ich das Gefühl, dass sie irritierend werden und schnell geschluckt werden, da JavaScript Javas Mechanismus für typisierte Fangklauseln fehlt. Die Verwendung von Ausnahmen (insbesondere als Kontrollfluss) war auch nie eine etablierte Praxis. All dies führt mich zu dem Verständnis, dass geprüfte Ausnahmen zu wenig bringen, während bessere und derzeit gebräuchlichere Möglichkeiten zur Darstellung des Scheiterns verfügbar sind (z. B. Gewerkschaftsrückkehr).

@gcneu
So wird es in C# gemacht, das Problem ist, dass Dokumente in Typoskript nicht standardmäßig sind.
Ich kann mich nicht erinnern, auf eine gut dokumentierte Definitionsdatei gestoßen zu sein. Die verschiedenen lib.d.ts Dateien enthalten Kommentare, aber diese enthalten keine geworfenen Fehler (mit einer Ausnahme: lib.es6.d.ts hat ein throws in Date[Symbol.toPrimitive](hint: string) ).

Außerdem berücksichtigt dieser Vorschlag das Ableiten von Fehlern, was nicht passiert, wenn Fehler aus Dokumentationskommentaren stammen. Bei abgeleiteten geprüften Ausnahmen muss der Entwickler nicht einmal die Klausel throws angeben, der Compiler leitet sie automatisch ab und verwendet sie für die Kompilierung und fügt sie der resultierenden Definitionsdatei hinzu.

Ich stimme zu, dass das Erzwingen der Fehlerbehandlung keine gute Sache ist, aber mit dieser Funktion werden nur mehr Informationen hinzugefügt, die dann von denen verwendet werden können, die dies wünschen.
Das Problem mit:

... gibt es bessere und derzeit gebräuchlichere Möglichkeiten, das Scheitern darzustellen

Dass es dafür keine Standardmethode gibt.
Sie könnten union return verwenden, @aleksey-bykov verwendet Tried<> , und ein Entwickler einer anderen Bibliothek eines Drittanbieters wird etwas völlig anderes tun.
Das Auslösen von Fehlern ist ein sprachübergreifender Standard (js, java, c# ...) und da es Teil des Systems und keine Problemumgehung ist, sollte es (meiner Meinung nach) eine bessere Handhabung in Typoskript haben, und ein Beweis dafür ist die Zahl von Problemen, die ich hier im Laufe der Zeit gesehen habe und die nach einer Typanmerkung in der catch -Klausel fragen.

Ich hätte gerne Informationen im Tooltip in VS, wenn eine Funktion (oder aufgerufene Funktion) auslösen kann. Für *.d.ts -Dateien brauchen wir wahrscheinlich seit TS2.0 einen gefälschten Parameter wie diesen .

@HolgerJeromin
Warum sollte es nötig sein?

Hier ist eine einfache Frage, welche Signatur sollte für dontCare im folgenden Code abgeleitet werden?

function mightThrow(): void throws string {
   if (Math.random() > 0.5) {
       throw 'hey!';
   }
}

function dontCare() {
   return mightThrow();
}

nach dem, was Sie in Ihrem Vorschlag gesagt haben, sollte es so sein

function dontCare(): void throws string {

Ich sage, es sollte ein Typfehler sein, da eine geprüfte Ausnahme nicht richtig behandelt wurde

function dontCare() { // <-- Checked exception wasn't handled.
         ^^^^^^^^^^

warum ist das so?

denn sonst besteht eine sehr gute Chance, dass der Zustand des unmittelbaren Aufrufers beschädigt wird:

class MyClass {
    private values: number[] = [];

    keepAllValues(values: number[]) {
       for (let index = 0; index < values.length; index ++) {
            this.values.push(values[index]); 
            mightThrow();
       }
    }
}

Wenn Sie eine Ausnahme durchschlüpfen lassen, können Sie nicht darauf schließen, dass sie aktiviert ist, da auf diese Weise der Verhaltensvertrag von keepAllValues verletzt würde (nicht alle Werte wurden trotz der ursprünglichen Absicht beibehalten).

Der einzig sichere Weg ist, sie sofort zu fangen und sie explizit erneut zu werfen

    keepAllValues(values: number[]) {
           for (let index = 0; index < values.length; index ++) {
                this.values.push(values[index]); 
                try {
                    mightThrow();
                } catch (e) {
                    // the state of MyClass is going to be corrupt anyway
                    // but unlike the other example this is a deliberate choice
                    throw e;
                }
           }
    }

Andernfalls können Sie, obwohl die Anrufer wissen, was getrunken werden kann, ihnen nicht garantieren, dass es sicher ist, mit dem Code fortzufahren, der gerade geworfen wurde

Es gibt also keine automatische Weitergabe von geprüften Ausnahmeverträgen

und korrigieren Sie mich, wenn ich falsch liege, das ist genau das, was Java tut, was Sie zuvor als Beispiel erwähnt haben

@aleksey-bykov
Dies:

function mightThrow(): void {
   if (Math.random() > 0.5) {
       throw 'hey!';
   }
}

function dontCare() {
   return mightThrow();
}

Bedeutet, dass sowohl mightThrow als auch dontCare auf throws string gefolgert werden, jedoch:

function dontCare() {
    try {
        return mightThrow();
    } catch (e: string) {
        // do something
    }
}

Wird keine throw -Klausel haben, da der Fehler behandelt wurde.
Dies:

function mightThrow(): void throws string | MyErrorType { ... }

function dontCare() {
    try {
        return mightThrow();
    } catch (e: string | MyErrorType) {
        if (typeof e === "string") {
            // do something
        } else { throw e }
    }
}

Wird throws MyErrorType haben.

Was Ihr keepAllValues Beispiel betrifft, bin ich mir nicht sicher, was Sie in Ihrem Beispiel meinen:

class MyClass {
    private values: number[] = [];

    keepAllValues(values: number[]) {
       for (let index = 0; index < values.length; index ++) {
            this.values.push(values[index]); 
            mightThrow();
       }
    }
}

MyClass.keepAllValues wird als throws string abgeleitet, weil mightThrow ein string auslösen könnte und dieser Fehler nicht behandelt wurde.

Was Ihr keepAllValues -Beispiel angeht, bin ich mir nicht sicher, was Sie meinen

Ich meinte, dass die Ausnahmen, die von mightThrow keepAllValues und es mitten in dem beenden, was es tut, und seinen Zustand beschädigt hinterlassen. Es ist ein Problem. Was Sie vorschlagen, ist, die Augen vor diesem Problem zu schließen und so zu tun, als wäre es nicht ernst. Ich schlage vor, dieses Problem anzugehen, indem verlangt wird, dass alle geprüften Ausnahmen sofort behandelt und explizit erneut ausgelöst werden. Auf diese Weise gibt es keine Möglichkeit, den Staat unbeabsichtigt zu korrumpieren. Und obwohl es immer noch beschädigt sein kann, wenn Sie sich dafür entscheiden, würde es einige absichtliche Codierung erfordern.

Denken Sie darüber nach, es gibt zwei Möglichkeiten, Ausnahmen zu behandeln:

  • unhandle sie, was zu einem Absturz führt, wenn der Absturz das ist, was Sie wollen, dann sind wir hier in Ordnung
  • Wenn Sie keinen Absturz wollen, brauchen Sie eine Anleitung, nach welcher Art von Ausnahme Sie suchen müssen, und hier kommt Ihr Vorschlag ins Spiel: geprüfte Ausnahmen - alle explizit aufgelistet, damit Sie sie alle behandeln können und nicht nichts verpassen

Wenn wir uns nun für die geprüften Ausnahmen entschieden haben, die ordnungsgemäß behandelt werden und einen Absturz verhindern, müssen wir eine Situation ausschließen, in der wir eine Ausnahme behandeln, die aus mehreren Schichten tief von der Stelle stammt, an der Sie sie abfangen:

export function calculateFormula(input) {
    return calculateSubFormula(input);
}
export function calculateSubFormula(input) {
   return calculateSubSubFormula(input);
}
export function calculateSubSubFormula(input): number throws DivisionByZero  {
   return 1/input;
}

try {
   calculateFormula(0);
} catch (e: DivisionByZero) {
   // it doesn't make sense to expose DivisionByZero from under several layers of calculations
   // to the top level where nothing we can do or even know what to do about it
   // basically we cannot recover from it, because it happened outside of our immediate reach that we can control
}

Das obige Beispiel bringt einen interessanten Fall zur Betrachtung, was die abgeleitete Signatur von wäre:

function boom(value: number) /* what comes here?*/  {
    return 1/value;
}

ein weiterer interessanter Fall

// 1.
function run<R, E>(callback(): R throws E) /* what comes here? */ {
    try {
        return callback();
    } catch (e: DivisionByZero) {
        // ignore
    }
}

function throw() { return 1 / 0; }

// 2.
run(throw); /* what do we expect here? */


@aleksey-bykov
Sie schlagen also vor, dass alle Fehler wie bei Java behandelt werden müssen?
Ich bin kein Fan davon (obwohl ich aus Java komme und es immer noch liebe), weil js/ts viel dynamischer sind und ihre Benutzer daran gewöhnt sind.
Es kann ein Flag sein, das Sie dazu bringt, mit Fehlern umzugehen, wenn Sie es beim Kompilieren einschließen (wie strictNullChecks ).

Mein Vorschlag ist nicht hier, um nicht behandelte Ausnahmen zu lösen, der von Ihnen gepostete Code wird jetzt ohne diese implementierte Funktion brechen, und es würde auch in js brechen.
Mein Vorschlag soll Sie als Entwickler auf die verschiedenen Fehler aufmerksam machen, die möglicherweise ausgelöst werden. Es liegt immer noch an Ihnen, ob Sie sie behandeln oder ignorieren.

Was die Division durch 0 betrifft, führt dies nicht zu einem Fehler:

console.log(1 / 0) // Infinity
console.log(1 / "hey!") // NaN

bewusster für die verschiedenen Fehler, die ausgelöst werden könnten

Es hat keinen Sinn, dies zu tun, es sei denn, sie können sich damit befassen. Der aktuelle Vorschlag ist aufgrund der von mir aufgeführten Fälle nicht durchführbar

Sie schlagen also vor, dass alle Fehler wie bei Java behandelt werden müssen?

Ja, das bedeutet es, Ausnahmen geprüft zu haben

@aleksey-bykov
Ich verstehe nicht, warum einer der von Ihnen aufgeführten Fälle diesen Vorschlag als undurchführbar erscheinen lässt.

Es ist kein Problem, einen Fehler zu behandeln, der weit nach unten in die Aufrufkette geworfen wurde, selbst wenn ich eine Funktion verwende, die auf das Werfen von DivisionByZero geschlossen wurde (unabhängig davon, wo sie geworfen wurde), kann ich mich dafür entscheiden, sie zu behandeln .
Ich kann versuchen, es mit anderen Argumenten erneut zu versuchen, ich kann dem Benutzer eine Meldung anzeigen, dass etwas schief gelaufen ist, ich kann dieses Problem protokollieren, damit ich später meinen Code ändern kann, um es zu behandeln (falls es häufig vorkommt).

Auch dieser Vorschlag ändert nichts an der Laufzeit, sodass alles, was funktioniert hat, weiterhin wie bisher funktioniert.
Der einzige Unterschied besteht darin, dass ich mehr Informationen über die möglicherweise ausgegebenen Fehler habe.

Ich verstehe, was Sie sagen, nichts wird zur Javascript-Laufzeit geändert, aber Ihre Botschaft hier soll den Benutzern die Illusion vermitteln, dass sie wissen, was sie tun, indem sie eine Ausnahme behandeln, die von 20 Ebenen weiter unten mit der gleichen Zuversicht kam wie Sie würden eine sofortige Ausnahme behandeln

Es gibt einfach keine Möglichkeit, ein Problem zu beheben, das 20 Schichten tiefer passiert ist

Sie können es natürlich wie jede ungeprüfte Ausnahme protokollieren, aber Sie können es nicht beheben

Also ist es im Allgemeinen eine Lüge, es gibt genug Lügen in TS, lasst uns die Leute nicht noch mehr verwirren

@aleksey-bykov
Was Sie beschreiben, existiert in allen Sprachen, die Ausnahmen unterstützen.
Niemand hat gesagt, dass das Abfangen einer Ausnahme das Problem beheben wird, aber Sie können damit elegant umgehen.

Zu wissen, welche Fehler beim Aufrufen einer Funktion ausgelöst werden können, hilft den Entwicklern, zwischen den Fehlern zu unterscheiden, die sie behandeln können, und denen, die sie nicht können.

Im Moment wissen Entwickler vielleicht nicht, dass die Verwendung JSON.parse einen Fehler auslösen könnte, aber wenn es Teil von lib.d.ts wäre und die IDE ihn (zum Beispiel) darüber informieren würde, würde er sich vielleicht dafür entscheiden diesen Fall behandeln.

Sie können ein Problem nicht behandeln, das 20 Schichten darunter aufgetreten ist, weil der interne Zustand in 19 Schichten beschädigt ist und Sie nicht dorthin gehen können, weil der Zustand privat ist

Um konstruktiv zu sein: Was ich vorschlage, ist, dass Benutzer geprüfte Ausnahmen sofort behandeln und explizit erneut auslösen. Auf diese Weise schließen wir unbeabsichtigte Verwirrung aus und trennen geprüfte Ausnahmen von ungeprüften:

  • überprüfte Ausnahme: ist in unmittelbarer Reichweite passiert und muss behandelt werden, auf diese Weise ist garantiert, dass der Zustand nicht korrupt ist und es sicher ist, fortzufahren
  • Ungeprüfte Ausnahme: In unmittelbarer Nähe oder viel weiter unten passiert, kann nicht behandelt werden, weil der Zustand korrupt war, Sie können es protokollieren oder auf eigene Gefahr fortfahren

SyntaxError in JSON.parse sollte als geprüfte Ausnahme deklariert werden

@aleksey-bykov

Ich verstehe nicht, warum es notwendig ist, Entwickler dazu zu zwingen, etwas zu tun, was sie nicht wollen, etwas, das sie bisher nicht getan haben.

Hier ist ein Beispiel:
Ich habe einen Webclient, in dem der Benutzer JSON-Daten schreiben/einfügen und dann auf eine Schaltfläche klicken kann.
Die App nimmt diese Eingabe und übergibt sie an eine Bibliothek eines Drittanbieters, die diesen JSON irgendwie analysiert und den JSON zusammen mit den verschiedenen Typen der Werte (String, Zahl, Boolean, Array usw.) zurückgibt.
Wenn diese Bibliothek eines Drittanbieters ein SyntaxError auslöst, kann ich mich erholen: Informieren Sie den Benutzer, dass seine Eingabe ungültig ist und er es erneut versuchen sollte.

Indem er weiß, welche Fehler beim Aufrufen einer Funktion ausgelöst werden können, kann der Entwickler entscheiden, was er behandeln kann/will und was nicht.
Es sollte keine Rolle spielen, wie tief der Fehler in die Kette geworfen wurde.

Schau, du scheinst nicht zu verstehen, was ich sage, wir drehen uns im Kreis

Indem Sie SyntaxError aus der Bibliothek eines Drittanbieters werfen lassen, setzen Sie Ihren Benutzer den Implementierungsdetails Ihres eigenen Codes aus, die gekapselt werden sollen

Im Grunde sagst du, hey, es ist nicht mein Code, der nicht funktioniert, es ist diese dumme Bibliothek, die ich im Internet gefunden und benutzt habe. Wenn du also ein Problem damit hast, kümmere dich um diese Bibliothek von Drittanbietern, nicht um mich, ich habe nur gesagt, worum ich gebeten wurde

und es gibt keine Garantie dafür, dass Sie die Instanz dieser 3rd-Bibliothek nach diesem SyntaxError noch verwenden können. Es liegt in Ihrer Verantwortung, dem Benutzer Garantien zu geben, z

Unterm Strich müssen Sie für die Behandlung innerer Ausnahmen verantwortlich sein ( nicht alle, nur die überprüften, ich bitte Sie )

Ich verstehe, was Sie sagen, aber ich stimme dem nicht zu.
Du hast recht, das ist im Grunde das, was ich sage.
Wenn ich eine Bibliothek eines Drittanbieters verwendet habe, die einen Fehler auslöst, kann ich mich entscheiden, damit umzugehen oder ihn zu ignorieren und den Benutzer meines Codes damit umgehen zu lassen.
Dafür gibt es viele Gründe, zum Beispiel ist die Bibliothek, die ich schreibe, UI-agnostisch, sodass ich den Benutzer nicht darüber informieren kann, dass etwas nicht stimmt, aber wer jemals meine Bibliothek verwendet, kann mit den Fehlern umgehen, die bei der Verwendung ausgegeben werden my lib und bearbeiten Sie sie, indem Sie mit dem Benutzer interagieren.

Wenn eine Bibliothek beim Auslösen in einem beschädigten Zustand zurückbleibt, muss sie dies wahrscheinlich dokumentieren.
Wenn ich dann eine solche Bibliothek verwende und dadurch ein Fehler darin auftritt, wird mein Zustand beschädigt, dann muss ich das dokumentieren.

Endeffekt:
Dieser Vorschlag bietet weitere Informationen zu ausgelösten Fehlern.
Es sollte Entwickler nicht dazu zwingen, Dinge anders zu machen, sondern es ihnen lediglich erleichtern, mit den Fehlern umzugehen, wenn sie dies wünschen.

Sie können anderer Meinung sein, es ist in Ordnung, nennen wir sie einfach nicht geprüfte Ausnahmen, denn so wie Sie es ausdrücken, sind geprüfte Ausnahmen nicht

Nennen wir sie aufgelistete oder aufgedeckte Ausnahmen, weil Sie sich nur darum kümmern, die Entwickler darauf aufmerksam zu machen

@aleksey-bykov
Na gut, Name geändert.

@aleksey-bykov

Sie können ein Problem nicht behandeln, das 20 Schichten darunter aufgetreten ist, weil der interne Zustand in 19 Schichten beschädigt ist und Sie nicht dorthin gehen können, weil der Zustand privat ist

Nein, Sie können den internen Zustand nicht reparieren, aber den lokalen Zustand könnten Sie sicherlich reparieren, und das ist genau der Punkt, an dem Sie ihn hier behandeln und nicht tiefer im Stapel.

Wenn Sie argumentieren, dass es keine Möglichkeit gibt, sicher zu sein, in welchem ​​​​Zustand sich einige gemeinsam genutzte veränderliche Werte befinden, wenn Sie die Ausnahme behandeln, dann ist dies ein Argument gegen die imperative Programmierung und nicht auf diesen Vorschlag beschränkt.

Wenn jede Schicht die Verantwortung dafür übernehmen muss, auf eine Ausnahme zu reagieren, die unmittelbar von einer darunter liegenden Schicht kommt, gibt es eine viel bessere Chance für eine erfolgreiche Wiederherstellung. Dies ist die Idee hinter den geprüften Ausnahmen, wie ich es sehe

Um es mit anderen Worten auszudrücken, Ausnahmen, die von mehr als 1 Ebene darunter kommen, sind ein Satz, es ist zu spät, etwas anderes zu tun, als die gesamte Infrastruktur von Grund auf neu zu instanziieren (wenn Sie Glück haben, gibt es keine globalen Überbleibsel, die Sie können ' nicht erreichen)

Der genannte Vorschlag ist meistens nutzlos, da es keine zuverlässige Möglichkeit gibt, auf das Wissen zu reagieren, dass etwas Schlimmes außerhalb Ihrer Reichweite passiert ist

Das ist toll. FWIW: Ich denke, wenn es hinzugefügt wird, sollte es standardmäßig erforderlich sein, Throwing-Methoden zu handhaben oder Ihre Methode auch als Throwing zu markieren. Ansonsten ist es so ziemlich nur Dokumentation.

@agonzalezjr
Ich denke, wie bei den meisten Funktionen in Typoskript sollten Sie sich auch für diese Funktion anmelden können.
So wie es nicht zwingend erforderlich ist, Typen hinzuzufügen, sollte es kein Muss sein, zu werfen/fangen.

Es sollte wahrscheinlich ein Flag geben, um es zu einem Muss zu machen, wie --onlyCheckedExceptions .

In jedem Fall wird diese Funktion auch verwendet, um die Typen von ausgelösten Ausnahmen abzuleiten/zu validieren, also nicht nur zur Dokumentation.

@nitzantomer

Hier ist ein Beispiel:
Ich habe einen Webclient, in dem der Benutzer JSON-Daten schreiben/einfügen und dann auf eine Schaltfläche klicken kann.
Die App nimmt diese Eingabe und übergibt sie an eine Bibliothek eines Drittanbieters, die diesen JSON irgendwie analysiert und den JSON zusammen mit den verschiedenen Typen der Werte (String, Zahl, Boolean, Array usw.) zurückgibt.
Wenn diese Bibliothek eines Drittanbieters einen SyntaxError auslöst, kann ich ihn beheben: Informieren Sie den Benutzer, dass seine Eingabe ungültig ist und er es erneut versuchen sollte.

Dies ist sicherlich ein Bereich, in dem die ganze Idee von geprüften Ausnahmen düster wird. Hier wird auch die Definition von _Ausnahmesituation_ unklar.
Das Programm in Ihrem Beispiel wäre ein Argument dafür, dass JSON.parse so deklariert wird, dass es eine geprüfte Ausnahme auslöst.
Was aber, wenn das Programm ein HTTP-Client ist und JSON.parse basierend auf dem Wert eines Headers aufruft, der an eine HTTP-Antwort angehängt ist, die zufällig einen falsch geformten Text enthält? Es gibt nichts Sinnvolles, was das Programm zur Wiederherstellung tun kann, alles, was es tun kann, ist erneutes Auslösen.
Ich würde sagen, dass dies ein Argument gegen die Deklaration von JSON.parse als aktiviert ist.

Es hängt alles vom Anwendungsfall ab.

Ich verstehe, dass Sie vorschlagen, dass dies unter einem Flag erfolgen soll, aber stellen wir uns vor, dass ich diese Funktion verwenden möchte, also habe ich das Flag aktiviert. Je nachdem, was für ein Programm ich schreibe, kann es mir entweder helfen oder mich behindern.

Auch die klassische java.io.FileNotFoundException ist ein Beispiel dafür. Es ist aktiviert, aber kann das Programm wiederhergestellt werden? Es hängt wirklich davon ab, was die fehlende Datei für den Anrufer bedeutet, nicht für den Angerufenen.

@aluanhaddad

Dieser Vorschlag schlägt nicht vor, neue Funktionen hinzuzufügen, sondern nur eine Möglichkeit hinzuzufügen, etwas in Typoskript auszudrücken, das bereits in Javascript vorhanden ist.
Fehler werden geworfen, aber Typoskript hat derzeit keine Möglichkeit, sie zu deklarieren (beim Werfen oder Fangen).

In Ihrem Beispiel kann das Programm durch Abfangen des Fehlers "anständig" fehlschlagen (z. B. indem dem Benutzer die Meldung "Etwas ist schief gelaufen" angezeigt wird), indem es diesen Fehler abfängt, oder es kann ihn je nach Programm / Entwickler ignorieren.
Wenn der Zustand des Programms durch diesen Fehler beeinträchtigt werden kann, kann die Behandlung einen gültigen Zustand anstelle eines fehlerhaften Zustands beibehalten.

In jedem Fall sollte der Entwickler entscheiden, ob er sich von einem geworfenen Fehler erholen kann oder nicht.
Es liegt auch an ihm, zu entscheiden, was eine Wiederherstellung bedeutet. Wenn ich beispielsweise diesen http-Client schreibe, der als Bibliothek eines Drittanbieters verwendet werden soll, möchte ich möglicherweise, dass alle Fehler, die von meiner Bibliothek ausgegeben werden, denselben Typ haben:

enum ErrorCode {
    IllFormedJsonResponse,
    ...
}
...
{
    code: ErrorCode;
    message: string;
}

Wenn ich jetzt in meiner Bibliothek die Antwort mit JSON.parse parse, möchte ich einen ausgelösten Fehler abfangen und dann meinen eigenen Fehler auslösen:

{
    code: ErrorCode.IllFormedJsonResponse,
    message: "Failed parsing response"
} 

Wenn diese Funktion implementiert ist, wird es für mich einfach sein, dieses Verhalten zu deklarieren, und den Benutzern meiner Bibliothek wird klar sein, wie es funktioniert und fehlschlägt.

Dieser Vorschlag schlägt nicht vor, neue Funktionen hinzuzufügen, sondern nur eine Möglichkeit hinzuzufügen, etwas in Typoskript auszudrücken, das bereits in Javascript vorhanden ist.

Ich weiss. Ich spreche von den Fehlern, die TypeScript unter diesem Vorschlag ausgeben würde.
Meine Annahme war, dass dieser Vorschlag eine Unterscheidung zwischen geprüften und ungeprüften Ausnahmebezeichnern (abgeleitet oder explizit) implizierte, wiederum nur für Typüberprüfungszwecke.

@aluanhaddad

Was Sie im vorherigen Kommentar gesagt haben:

Was aber, wenn das Programm ein HTTP-Client ist und JSON.parse basierend auf dem Wert eines Headers aufruft, der an eine HTTP-Antwort angehängt ist, die zufällig einen falsch geformten Text enthält? Es gibt nichts Sinnvolles, was das Programm zur Wiederherstellung tun kann, alles, was es tun kann, ist erneutes Auslösen.

Dasselbe gilt für die Rückgabe von null , wenn meine Funktion deklariert ist, ein Ergebnis zurückzugeben.
Wenn sich der Entwickler für die Verwendung strictNullChecks entscheidet, können Sie genau dasselbe sagen, wenn die Funktion ein null zurückgibt (anstatt zu werfen), dann gibt es im selben Szenario „das Programm kann nichts Sinnvolles tun sich erholen".

Aber auch ohne die Verwendung eines onlyCheckedExceptions -Flags ist diese Funktion immer noch nützlich, da sich der Compiler zum Beispiel beschwert, wenn ich versuche, den Fehler als string , wenn die Funktion deklariert ist, nur Error .

Nette Idee, es wäre hilfreich, aber nicht streng/typsicher, da es keine Möglichkeit gibt, zu wissen, was verschachtelte Aufrufe auf Sie werfen könnten.

Das heißt, wenn ich eine Funktion habe, die möglicherweise eine Ausnahme vom Typ A auslöst, aber darin eine verschachtelte Funktion aufrufe und sie nicht in try catch einfüge, wird sie ihre Ausnahme vom Typ B an meinen Aufrufer werfen.
Wenn der Aufrufer also nur Ausnahmen vom Typ A erwartet, gibt es keine Garantie, dass er keine anderen Typen von verschachtelten Ausnahmen erhält.

(Der Thread ist zu lang, also sorry, falls ich diesen Kommentar übersehen habe)

@shaipetel
Der Vorschlag besagt, dass der Compiler die Arten von nicht behandelten Fehlern ableiten und sie der Funktions-/Methodensignatur hinzufügen wird.
In dem von Ihnen beschriebenen Fall wird Ihre Funktion also A | B , falls B nicht behandelt wurde.

Ach, ich verstehe. Es wird alle Methoden durchgehen, die ich aufrufe, und alle möglichen Ausnahmetypen sammeln?
Ich würde es gerne sehen, wenn es möglich ist. Sehen Sie, ein Entwickler kann immer eine unerwartete Ausnahme haben, die nicht deklariert wird, in diesem Fall sind ein "Objekt nicht auf Instanz gesetzt" oder "durch 0 teilen" oder ähnliche Ausnahmen immer fast von jeder Funktion möglich.
IMHO wäre es am besten wie in C # gehandhabt worden, wo alle Ausnahmen von einer Basisklasse erben, die eine Nachricht enthält, und das Werfen von unverpacktem Text oder anderen Objekten überhaupt nicht zuzulassen. Wenn Sie Basisklasse und Vererbung haben, können Sie Ihre Fänge kaskadieren und Ihren erwarteten Fehler in einem Block und andere unerwartete in einem anderen behandeln.

@shaipetel
In Javascript basieren alle Fehler auf der Klasse Error , aber Sie sind nicht darauf beschränkt, Fehler zu werfen, Sie können alles Mögliche werfen:

  • throw "something went wrong"
  • throw 0
  • throw { message: "something went wrong", code: 4 }

Ja, ich weiß, wie JavaScript funktioniert, wir diskutieren über TypeScript, das weitere Einschränkungen mit sich bringt.
Ich schlug vor, dass IMHO eine gute Lösung wäre, TypeScript der Ausnahmebehandlung folgen zu lassen, die erfordert, dass alle ausgelösten Ausnahmen einer bestimmten Basisklasse angehören und nicht das direkte Werfen von unverpackten Werten zulassen.

Es wird also nicht "throw 0" oder "throw 'some error'" zulassen.
So wie JavaScript viele Dinge zulässt, die TypeScript nicht zulässt.

Danke,

@nitzantomer

Gilt dasselbe für die Rückgabe einer Null, wenn meine Funktion deklariert wird, um ein Ergebnis zurückzugeben.
Wenn sich der Entwickler für die Verwendung von strictNullChecks entscheidet, können Sie genau das Gleiche sagen, wenn die Funktion eine Null zurückgibt (anstatt zu werfen), dann im selben Szenario "es gibt nichts Sinnvolles, das das Programm zur Wiederherstellung tun kann".

Aber auch ohne die Verwendung eines onlyCheckedExceptions-Flags ist diese Funktion immer noch nützlich, da sich der Compiler beispielsweise beschwert, wenn ich versuche, den Fehler als Zeichenfolge abzufangen, wenn die Funktion deklariert ist, nur einen Fehler auszulösen.

Ich verstehe, was du sagst, das macht Sinn.

@shaipetel Wie bereits in diesem Vorschlag und an anderer Stelle besprochen, funktioniert das Unterklassen von integrierten Funktionen wie Error und Array nicht. Dies führt zu einem unterschiedlichen Verhalten bei weit verbreiteten Laufzeiten und Kompilierungszielen.

Das Abfangen von Werten verschiedener Nicht-Fehlertypen ist die praktikabelste Möglichkeit, sich die vorgeschlagene Funktionalität vorzustellen. Ich sehe das eigentlich nicht als Problem an, da Mechanismen zur Ausbreitung von Ausnahmen, die diese Funktion nutzen, wahrscheinlich zu Fehlern führen würden, die viel spezifischer und viel hilfreicher sind als beispielsweise ein Stack-Trace oder andere fehlerspezifische Eigenschaften.
Eine Verlängerung Error ist nicht möglich. Es ist nicht realisierbar, bis alle Ziele unter es2015 nicht mehr verwendet werden.

Wenn dieser Vorschlag indirekt zu mehr Unterklassen der Funktion Error führt, dann halte ich das für eine schlechte Idee. Ein solcher Einwand ist völlig getrennt von allen philosophischen Einwänden bezüglich der Verwendung von Ausnahmen für den Kontrollfluss oder der Definition außergewöhnlicher Umstände. Wenn dieser Vorschlag angenommen würde, würde ich daher eine extrem laute und unverblümte Dokumentation bezüglich der korrekten Verwendung und der Notwendigkeit erwarten, Unterklassen Error zu vermeiden.

Ich würde es lieben, wenn es eine Art Hilfslogik für die Behandlung von typisierten Ausnahmen geben würde. Ich refaktoriere viel Versprechungscode, um async/await zu verwenden, was derzeit so aussieht:

doSomethingWhichReturnsPromise()
    .then(send(200))
    .catch(NotFoundError, (error) => { send(404); })
    .catch(SomeBadDataError, (error) => { send(400); })
    .catch(CantSeeThisError, (error) => { send(403); })
    .catch((error) => { send(500); })

In der neuen Welt sieht es dann so aus:

{
    await doSomethingWhichReturnsPromise();
    send(200);
}
catch(error)
{
    if(error instanceof NotFoundError) { send(404); } 
    else if(error instanceof SomeBadDataError) { send(400); } 
    else if(error instanceof CantSeeThisError) { send(403); } 
    else { send(500); } 
}

Was in Ordnung ist, aber mehr Code erfordert und in gewisser Weise etwas weniger lesbar ist, daher wäre es großartig, wenn es eine Form der Unterstützung für Folgendes gäbe:

{
    await doSomethingWhichReturnsPromise();
    send(200);
}
catch(NotFoundError, error) { send(404); }
catch(SomeBadDataError, error) { send(404); }
catch(CantSeeThisError, error) { send(404); }
catch(error) { send(404); }

Was das vorherige Bit ausgeben würde, aber als syntaktischer Zucker könnten Sie es sogar als Generika tun, aber es ist etwas böser:

{
    await doSomethingWhichReturnsPromise();
    send(200);
}
catch<NotFoundError>(error) { send(404); }
catch<SomeBadDataError>(error) { send(404); }
catch<CantSeeThisError>(error) { send(404); }
catch(error) { send(404); }

@grofit
Obwohl ich es gerne hätte, wenn Typoskript das unterstützen würde, was Sie vorschlagen, hat es meiner Meinung nach nichts mit diesem Problem zu tun.
Was Sie vorschlagen, kann sogar implementiert werden, ohne zu implementieren, worum es bei diesem Problem geht, nur dass der Compiler sich nicht beschweren kann (zum Beispiel), dass NotFoundError nicht ausgelöst wird.

Ich dachte, dies sei ein Vorschlag für getippte Fänge, kein Problem irgendeiner Art, ich wollte nur keine Threads duplizieren, werde es dann in einer eigenen Ausgabe veröffentlichen.

@grofit
Typisierte Catches sind ebenfalls Teil der Funktionsanforderung, aber auch die Möglichkeit, den Compiler darüber zu informieren, welche Art von Fehlern von Funktionen ausgelöst werden können (und dann kann der Compiler auch ableiten, welche Arten von Fehlern ausgelöst werden können).

Meiner Meinung nach können typisierte Fänge ohne die anderen Teile implementiert werden. Eröffnen Sie ein neues Problem, vielleicht entscheidet sich das TS-Team, es als Duplikat zu markieren, ich weiß es nicht.

@grofit

Was das vorherige Bit ausgeben würde, aber als syntaktischer Zucker könnten Sie es sogar als Generika tun, aber es ist etwas böser:

try
{
  await doSomethingWhichReturnsPromise();
  send(200);
}
catch<NotFoundError>(error) { send(404); }
catch<SomeBadDataError>(error) { send(404); }
catch<CantSeeThisError>(error) { send(404); }
catch(error) { send(404); }

wird nicht funktionieren, da es die Generierung von Code impliziert, der nur auf Typen basiert, während

 doSomethingWhichReturnsPromise()
    .then(send(200))
    .catch(NotFoundError, (error) => { send(404); })
    .catch(SomeBadDataError, (error) => { send(400); })
    .catch(CantSeeThisError, (error) => { send(403); })
    .catch((error) => { send(500); })

übergibt die Error-Funktion in jedem Fall und wird wahrscheinlich mit instanceof implementiert. Code wie dieser ist giftig, da er dazu anregt, Error zu verlängern, was eine sehr schlechte Sache ist.

@nitzantomer Ich stimme zu, dass es sich um ein separates Problem handelt. Das OP enthält Beispiele für Fangtypen, die Error nicht erweitern.

@aluanhaddad
Für das, wonach @grofit fragt, können Sie dann Folgendes tun:

try {
    // do something
} catch (isNotFoundError(error)) {
    ...
}  catch (isSomeBadDataError(error)) {
    ...
} catch (error) {
    ...
}

Wobei die isXXX(error) Typwächter in der Form von:
function isXXX(error): error is XXX { ... }

@nitzantomer sicher, aber Typwächter sind nicht das Problem. Das Problem ist

class MyError extends Error {
  myErrorInfo: string;
}

was problematisch ist, aber hier schon diskutiert wurde. Ich möchte den Punkt nicht vertiefen, aber viele große und bekannte Codebasen wurden durch die Übernahme dieser schlechten Praxis negativ beeinflusst. Die Verlängerung Error ist eine schlechte Idee.

@aluanhaddad
Ich bin mir dessen bewusst, weshalb ich eine Möglichkeit angeboten habe, das zu erreichen, was @grofit verlangt hat, aber ohne die Notwendigkeit, Error zu verlängern. Ein Entwickler kann Error immer noch erweitern, wenn er möchte, aber der Compiler wird in der Lage sein, js-Code zu generieren, der instanceof nicht verwenden muss

@nitzantomer Mir ist klar, dass Sie sich dessen bewusst sind, aber mir war nicht klar, was Sie @grofit vorgeschlagen haben, was nach einer netten Idee klingt.

Ich schlage nur ein totes Pferd, weil ich es nicht mag, mich mit APIs auseinanderzusetzen, die mich dazu bringen wollen, solche Muster zu verwenden. Trotzdem tut es mir leid, wenn ich diese Diskussion vom Thema abgekommen bin.

Wurde in dieser Diskussion noch weitergedacht? Ich würde gerne eine throws -Klausel in Maschinenschrift sehen.

@aluanhaddad Du sagst immer wieder, dass das Erweitern Error eine schlechte Praxis ist. Kannst du das etwas näher erläutern?

Es wurde ausführlich diskutiert. Grundsätzlich können Sie eingebaute Typen nicht zuverlässig erweitern. Das Verhalten variiert dramatisch über die Kombination von Umgebungen und --target Einstellungen.

Der einzige Grund, warum ich gefragt habe, ist, dass Ihre Kommentare das erste Mal waren, dass ich davon gehört habe. Nachdem ich ein bisschen gegoogelt hatte, fand ich nur Artikel, die erklärten, wie und warum Error verlängert _sollte_.

Nein, es wird nicht funktionieren. Sie können darüber ausführlich in https://github.com/Microsoft/TypeScript/issues/12123 und den zahlreichen verlinkten Issues nachlesen.

Es wird funktionieren, aber nur in "spezifischen Umgebungen", die bei der Anpassung von ES6 zur Mehrheit werden.

@nitzantomer : Glauben Sie, dass es möglich ist, einen TSLint-Check zu implementieren, der den Entwickler informiert, wenn eine Funktion mit @throws ohne umgebendes try/catch aufgerufen wird?

@bennyn Ich bin ein TSLint-Benutzer, aber ich habe nie versucht, neue Regeln zu erstellen.
Ich weiß wirklich nicht, ob es möglich ist (obwohl ich denke, dass es so ist), und wenn ja, wie einfach.

Wenn Sie es versuchen, aktualisieren Sie bitte hier, danke.

@shaipetel (und auch @nitzantomer)

Betreff:

Ja, ich weiß, wie JavaScript funktioniert, wir diskutieren über TypeScript, das weitere Einschränkungen mit sich bringt.
Ich schlug vor, dass IMHO eine gute Lösung wäre, TypeScript der Ausnahmebehandlung folgen zu lassen, die erfordert, dass alle ausgelösten Ausnahmen einer bestimmten Basisklasse angehören und nicht das direkte Werfen von unverpackten Werten zulassen.
Es wird also nicht "throw 0" oder "throw 'some error'" zulassen.
So wie JavaScript viele Dinge zulässt, die TypeScript nicht zulässt.

Ich bin mir nicht sicher, ob Sie @shaipetel so vorgeschlagen haben, aber nur für den Fall ... Ich würde davor warnen, dass Typescript throw darauf beschränkt, nur Error s zurückzugeben. Die kommenden Async-Rendering-Funktionen von React arbeiten unter der Haube von throw ing Promise s! (Klingt seltsam, ich weiß ... aber ich habe gelesen, dass sie dies im Vergleich zu async / await und yield / yield* Generatoren für ihren Anwendungsfall bewertet haben und ich bin mir sicher, was sie tun!)

throw ing Error s ist sicher 99 % der Anwendungsfälle da draußen, aber nicht 100 %. Ich denke nicht, dass Typescript Benutzer darauf beschränken sollte, nur throw Dinge zu schreiben, die eine Error Basisklasse erweitern. Es ist sicherlich viel fortgeschrittener, aber throw hat andere Anwendungsfälle als Fehler/Ausnahmen.

@mikeleonard
Ich stimme zu, es gibt viele Beispiele für bestehenden js-Code, der eine Vielzahl von Typen auslöst (ich habe viele throw "error message string" gesehen).
Das neue React-Feature ist ein weiteres gutes Beispiel.

Nur meine zwei Cent, ich konvertiere Code in async/await und werde so einem Throw unterzogen (nebenbei, ich hasse Ausnahmen). Eine Throws-Klausel zu haben, wie diese Ausgabe diskutiert, wäre meiner Meinung nach schön. Ich denke, es wäre auch nützlich, wenn "throws any" erlaubt wäre. (Außerdem vielleicht eine "nothrows"- und Compiler-Option, die die Dinge standardmäßig auf "nothrows" setzt.)

Es scheint eine natürliche Erweiterung zu sein, mit der Sie eingeben können, was eine Funktion auslöst. Rückgabewerte können optional in TS eingegeben werden, und für mich fühlt es sich an, als wäre Throw nur eine andere Art von Rückgabe (wie alle Alternativen zeigen, die vorgeschlagen werden, um Throw zu vermeiden, wie https://stackoverflow.com/a/39209039/162530).

(Persönlich hätte ich auch gerne die (optionale) Compiler-Option, um zu erzwingen, dass jeder Aufrufer einer als Throws deklarierten Funktion entweder auch als Throws deklarieren oder sie abfangen muss.)

Mein aktueller Anwendungsfall: Ich möchte nicht meine gesamte [Angular]-Codebasis konvertieren, um Ausnahmen für die Fehlerbehandlung zu verwenden (da ich sie hasse). Ich verwende async/await in den Implementierungsdetails meiner APIs, konvertiere aber throw in normale Promises/Observables, wenn eine API zurückkehrt. Es wäre schön, wenn der Compiler prüfen könnte, ob ich die richtigen Dinge fange (oder idealerweise überhaupt).

@aleksey-bykov Lassen Sie uns die Natur von JavaScript nicht ändern, sondern nur die Eingabe hinzufügen. :)

das Hinzufügen von Eingaben ändert es bereits (schneidet Code aus, der keinen Sinn ergibt),
Auf die gleiche Weise können wir die Ausnahmebehandlung straffen

Am Donnerstag, 26. Juli 2018, 20:22 Uhr schrieb Joe Pea [email protected] :

@aleksey-bykov https://github.com/aleksey-bykov Lass uns das nicht ändern
Natur von JavaScript, lassen Sie uns einfach die Eingabe hinzufügen. :)


Sie erhalten dies, weil Sie erwähnt wurden.
Antworten Sie direkt auf diese E-Mail und zeigen Sie sie auf GitHub an
https://github.com/Microsoft/TypeScript/issues/13219#issuecomment-408274156 ,
oder den Thread stumm schalten
https://github.com/notifications/unsubscribe-auth/AA5PzfUNS5E093Z74WA4WCUaTyWRRZC3ks5uKl1FgaJpZM4LXwLC
.

Wenn Sie für Versprechungen then Verkettung anstelle von async/await verwenden und die Verwendung throw vermeiden, funktioniert alles eigentlich ganz gut. Hier ist eine Proof-of-Concept-Skizze:

https://bit.ly/2NQZD8i - Playground-Link

interface SafePromise<T, E> {
    then<U, E2>(
        f: (t: T) => SafePromise<U, E2>):
        SafePromise<U, E | E2>;

    catch<U, E2>(
        f: (e: E) => SafePromise<U, E2>):
        SafePromise<U, E2>

    catch<U, E1, E2>(
        guard: (e: any) => e is E1,
        f: (e: E) => SafePromise<U, E2>):
        SafePromise<U, Exclude<E, E1> | E2>
}

declare function resolve<T>(t:T): SafePromise<T, never>
declare function reject<E extends Error>(e:E): SafePromise<never, E>


class E404 extends Error {
    code:404 = 404;
}

class E403 extends Error {
    code:403 = 403;
    static check(e: any): e is E403 { return e && e.code === 403 }
}

let p = resolve(20)


let oneError = p.then(function f(x) {
    if (x > 5) return reject(new E403())
    return resolve(x)
})


let secondError = oneError.then(x => {
    if (x > 10) return reject(new E404())
    return resolve(x)
})

let remove403 = secondError.catch(E403.check, e => {
    return resolve(25)
})

let moreErrorsInfer = p.then(x => {
    if (x > 5) return reject(new E403())
    if (x > 10) return reject(new E404())
    return resolve(x)
})

let moreErrorsNoInfer = p.then((x):SafePromise<number, E403|E404> => {
    if (x > 5) return reject(new E403())
    if (x > 10) return reject(new E404())
    return resolve(x)
})

Das Prädikat catch entfernt Typen aus der Vereinigung, während die Rückgabe von reject() ihnen hinzufügt. Das einzige, was nicht ganz funktioniert, ist die Inferenz von Union-Parameter-Typen (wahrscheinlich ein Fehler)

Im obigen Beispiel behandelte Probleme:

  • Es ist nicht auf Fehler beschränkt, Sie können alles ablehnen (obwohl wahrscheinlich eine schlechte Idee).
  • Es ist nicht auf die Verwendung von instanceof beschränkt, Sie können jeden Prädikattypwächter verwenden, um Fehler aus der Union zu entfernen

Das Hauptproblem, das ich sehe, wenn ich dies für den gesamten Code verwende, sind Callback-Signaturen. Damit es auf kompatible Weise funktioniert, wäre der standardmäßige "Rückgabetyp" "might-throw-any", und wenn Sie dies einschränken möchten, würden Sie sagen throws X oder throws never falls nicht zB

declare function pureMap<T>(t:T[], f: (x:T) => U throws never):U[] throws never

Die Version ohne Signatur:

declare function dirtyMap<T>(t:T[], f: (x:T) => U):U[]

würde eigentlich standardmäßig

declare function dirtyMap<T>(t:T[], f: (x:T) => U throws any):U[] throws any

um sicherzustellen, dass der gesamte aktuelle Code kompiliert wird.

Anders als bei strictNullChecks , wo Null-Rückgabewerte ziemlich ungewöhnlich sind, denke ich, dass Ausnahmen in JS ziemlich allgegenwärtig sind. Sie in .d.ts -Dateien zu modellieren, ist vielleicht nicht so schlimm (importieren Sie Typen aus Abhängigkeiten, um Ihre Fehler zu beschreiben), aber es wird definitiv ein nicht trivialer Aufwand sein und zu großen Vereinigungen führen.

Ich denke, ein guter Mittelweg wäre es, sich auf Promises und async/await zu konzentrieren, da ein Promise bereits ein Wrapper ist und asynchroner Code dort ist, wo sich die Fehlerbehandlung in typischen Szenarien am meisten verzweigt. Andere Fehler wären "ungeprüft"

@spion-h4 kann dies (bereits / wird) auf Typoskript verwendet werden?

declare function dirtyMap<T>(t:T[], f: (x:T) => U throws any):U[] throws any

@bluelovers
Nein, Typoskript unterstützt derzeit das Schlüsselwort throws nicht, darum geht es in diesem Problem.

Etwas zu beachten (nicht dass es diesen Vorschlag unbedingt blockiert) ist, dass ohne nominelle Klassen das Eingeben eingebauter Fehler immer noch nicht besonders nützlich ist, da sie alle strukturell gleich sind.

z.B:

class AnotherError extends Error {}

function* range(min: number, max: number): Iterable<number> throws TypeError, RangeError {
  if (typeof min !== 'number') {
    throw new TypeError('min must be a number')
  }
  if (typeof min !== 'number') {
    throw new TypeError('max must be a number')
  }
  if (!Number.isSafeInteger(min)) {
    // Allowed because without nominal types we can't distinguish
    // Error/RangeError/TypeError/etc
    throw new Error('min must be a safe integer')
  }
  if (!Number.isSafeInteger(max)) {
    // Also allowed because AnotherError is also structurally
    // compatible with TypeError/RangeError
    throw new AnotherError('max must be a safe integer')
  }
  for (let i = min; i < max; i++) {
    yield i
  }
}

@ Jamesernator
Diese Frage hat nichts mit dem Vorschlag zu tun.
Sie können genau das gleiche Problem bekommen, wenn Sie dies tun:

class BaseClass {
    propA!: string;
}

class MyClass1 extends BaseClass { }

class MyClass2 extends BaseClass { }

function fn(): MyClass1 {
    return new MyClass2();
}

Es ist eine Sprachbeschränkung, die sich auf viele Anwendungsfälle auswirkt.

Es gibt eine großartige Lektüre von @ahejlsberg zu getippten Ausnahmen: https://www.artima.com/intv/handcuffs.html

Ich glaube, dass TypeScript in einer guten Position ist, um diese Probleme zu vermeiden. Bei TypeScript dreht sich alles um _pragmatische_ Lösungen für _reale_ Probleme. Meiner Erfahrung nach ist die Fehlerbehandlung in großen JavaScript- und TypeScript-Codebasen eines der größten Probleme – zu sehen, welche Fehlerfunktionen _möglicherweise_ auslösen und ich _möglicherweise_ behandeln möchte, ist unglaublich schwierig. Dies ist nur möglich, indem man gute Dokumentation liest und schreibt (wir alle wissen, wie gut wir in diesem /s sind) oder sich die Implementierung ansieht, und da Ausnahmen im Gegensatz zu Rückgabewerten nur automatisch durch Funktionsaufrufe weitergegeben werden, reicht es nicht aus, nur Überprüfen Sie die direkt aufgerufene Funktion, aber um sie alle zu erfassen, müssten Sie auch verschachtelte Funktionsaufrufe überprüfen. _Dies ist ein reales Problem_.

Fehler in JavaScript sind eigentlich sehr nützliche Werte. Viele APIs in NodeJS werfen detaillierte Fehlerobjekte, die gut definierte Fehlercodes haben und nützliche Metadaten verfügbar machen. Beispielsweise haben Fehler von child_process.execFile() Eigenschaften wie exitCode und stderr , Fehler von fs.readFile() haben Fehlercodes wie ENOENT (Datei nicht gefunden ) oder EPERM (unzureichende Berechtigungen). Ich kenne viele Bibliotheken, die dies auch tun, z. B. gibt Ihnen der Datenbanktreiber pg genügend Metadaten zu einem Fehler, um zu wissen, welche genaue Spaltenbeschränkung dazu geführt hat, dass ein INSERT fehlgeschlagen ist.

Sie können eine besorgniserregende Menge an spröden Regex-Prüfungen bei Fehlermeldungen in Codebasen sehen, weil die Leute nicht wissen, dass Fehler richtige Fehlercodes haben und was sie sind.

Wenn wir in @types -Deklarationen und lib.d.ts definieren könnten, welche Fehler diese Funktionen auslösen können, hätte TypeScript die Macht, uns bei der Struktur des Fehlers zu helfen – welche möglichen Fehler es geben kann, was die Fehlercodewerte sind, welche Eigenschaften sie haben. Hier geht es _nicht_ um typisierte Ausnahmen und vermeidet daher alle Probleme mit typisierten Ausnahmen. Es ist eine _pragmatische Lösung_ für ein _reales Problem_ (im Gegensatz zu der Aufforderung, stattdessen monadische Fehlerrückgabewerte zu verwenden - die Realität von JavaScript-Land sieht anders aus, Funktionen werfen Fehler).

Die Anmerkung kann vollständig optional sein (oder mit einem Compiler-Flag erforderlich gemacht werden). Wenn nicht in einer Deklarationsdatei angegeben, löst eine Funktion einfach any (oder unknown ) aus.
Eine Funktion kann mit throws never manuell deklarieren, niemals auszulösen.
Wenn die Implementierung verfügbar ist, wirft eine Funktion eine Vereinigung aller Ausnahmetypen der Funktionen, die sie aufruft, und ihrer eigenen throw -Anweisungen, die sich nicht innerhalb eines try -Blocks mit einem catch befinden
Wenn einer von ihnen any wirft, wirft die Funktion auch any - das ist in Ordnung, an jeder Funktionsgrenze hat der Entwickler die Möglichkeit, dies durch eine explizite Anmerkung zu korrigieren.
Und in vielen, vielen Fällen, in denen eine einzelne bekannte Funktion aufgerufen und in einen try/catch-Befehl eingeschlossen wird (z. B. beim Lesen einer Datei und dem Umgang damit, dass sie nicht gefunden wird), kann TypeScript dann den Typ in der catch-Klausel ableiten.

Wir brauchen dafür weder Fehlerunterklassen noch instanceof - Fehlertypen können Schnittstellen sein, die Fehlercodes mit Zeichenfolgenliteralen angeben, und TypeScript kann die Vereinigung des Fehlercodes unterscheiden oder Typwächter verwenden.

Fehlertypen definieren

interface ExecError extends Error {
  status: number
  stderr: Buffer
}
function execFileSync(cmd: string): Buffer throws ExecError;
interface NoEntityError extends Error { code: 'ENOENT' }
interface PermissionError extends Error { code: 'EPERM' }
function readFileSync(file: string): Buffer throws NoEntityError | PermissionError;



md5-818797fe8809b5d8696f479ce1db4511



Preventing a runtime error due to type mismatch



md5-c2d214f4f8ecd267a9c9252f452d6588



Catching errors with type switch



md5-75d750bbe0c3494376581eaa3fa62ce5



```ts
try {
  const resp = await fetch(url, { signal })
  if (!resp.ok) {
    // inferred error type
    // look ma, no Error subclassing!
    throw Object.assign(new Error(resp.statusText), { name: 'ResponseError', response })
  }
  const data = await resp.json()
} catch (err) { // AbortError | Error & { name: 'ResponseError', response: Response } | SyntaxError
  switch (err.name)
    case 'AbortError': return; // Don't show AbortErrors
    default: displayError(err); return;
  }
}



md5-a859955ab2c42d8ce6aeedfbb6443e93



```ts
interface HttpError extends Error { status: number }
// Type-safe alternative to express-style middleware request patching - just call it (TM)
// The default express error handler recognises the status property
function checkAuth(req: Request): User throws HttpError {
    const header = req.headers.get('Authorization')
    if (!header) {
        throw Object.assign(new Error('No Authorization header'), { status: 401 })
    }
    try {
        return parseHeader(header)
    } catch (err) {
        throw Object.assign(new Error('Invalid Authorization header'), { status: 401 })
    }
}

Abgesehen von Fehlern ist es auch möglich, andere Arten von Nebeneffekten wie z

  • Divergenz (Endlosschleife / nicht zurückkehrend)
  • IO
  • Nichtdeterminismus ( Math.random )

Es erinnert mich an Koka von MSR, das Effekte auf zurückkehrende Typen markieren kann.

Der Antrag:

function square(x: number): number &! never { // This function is pure
  return x * x
}

function square2(x : number) : number &! IO {
  console.log("a not so secret side-effect")
  return x * x
}

function square3( x : number ) : number &! Divergence {
  square3(x)
  return x * x
}

function square4( x : number ) : number &! Throws<string> { // Or maybe a simple `number &! Throws`?
  throw "oops"
  return x * x
}

function map<T, R, E>(a: T[], f :(item: T) => R &! E): R[] &! E { ... }
function map<T, R>(a: T[], f :(item: T) => R): R[] { ... } // will also work; TS would collect side effects

function str<T>(x: T): string &! (T.toString.!) // care about the side effect of a type

Ich liebe die Idee der Deklaration des Fehlertyps! Dies wäre eine große Hilfe für Leute, die es brauchen, und wird nichts Schlechtes für Leute tun, die es nicht mögen. Ich habe jetzt das Problem in meinem Node-Projekt, das mit diesem Feature schneller gelöst werden konnte. Ich muss Fehler abfangen und korrekten http-Code zurücksenden - dazu muss ich immer alle Funktionsaufrufe überprüfen, um Ausnahmen zu finden, die ich behandeln muss - und das macht keinen Spaß -_-

PS. Die Syntax in https://github.com/Microsoft/TypeScript/issues/13219#issuecomment -428696412 sieht sehr gut aus - &! und das Umschließen von Fehlertypen in <> ist einfach erstaunlich.

Ich bin auch dafür. Jetzt sind alle Fehler untypisiert und das ist ziemlich ärgerlich. Obwohl ich hoffe, dass die meisten throws aus dem Code abgeleitet werden können, wenn wir ihn überall schreiben müssen.

Auf diese Weise können Benutzer sogar Tslint-Regeln schreiben, die den Entwickler zwingen, benutzerunfreundliche Fehler auf Ruheendpunkten usw. abzufangen.

Ich war tatsächlich überrascht, dass diese Funktionalität nicht _bereits_ in TypeScript enthalten war. Eines der ersten Dinge, die ich erklären wollte. Es wäre in Ordnung, wenn der Compiler dies erzwingt oder nicht, solange Sie die Information erhalten, dass ein geworfener Fehler auftritt und welche Art von Fehlern geworfen werden.

Selbst wenn wir die &! nicht bekommen und nur die Throws<T> bekommen, wäre das :+1:

Genau das habe ich vor, es wäre so viel schöner, wenn TypeScript diese Klauseln unterstützt.

Ein weiteres mögliches Feature wäre, irgendwie (vielleicht auf Funktionsebene oder auf Modulebene) strictExceptionTracking zu erzwingen – was bedeuten würde, dass Sie alles, was als throws X deklariert ist, mit einem Ausdruck try! invokeSomething() aufrufen müssten wie in Rust und Swift.

Es wird einfach zu invokeSomething() kompiliert, aber die Möglichkeit von Ausnahmen wird im Code sichtbar sein, wodurch es einfacher wird, zu erkennen, ob Sie etwas Veränderliches in einem schlechten vorübergehenden Zustand belassen (oder zugewiesene Ressourcen ungenutzt lassen).

@be5invis

function square(x: number): number &! never { // This function is pure
  return x * x
}
...

Ich gebe hier nur meinen Senf, aber das &! sieht für mich unglaublich hässlich aus.
Ich denke, Leute, die neu in der Maschinenschrift sind, würden von diesem Symbol irgendwie abgeschreckt werden, es ist wirklich nicht einladend.
Ein einfaches throws ist meiner Meinung nach expliziter, intuitiver und einfacher.

Ansonsten +1 für getippte Ausnahmen. 👍

Ich möchte meine +1 für getippte und geprüfte Ausnahmen hinzufügen.
Ich habe in meinem Code großen Gebrauch von typisierten Ausnahmen gemacht. Ich bin mir der Fallstricke von instanceof voll bewusst, ich habe es tatsächlich zu meinem Vorteil genutzt, indem ich in der Lage war, generische Handler für verwandte Fehler zu schreiben, die von einer gemeinsamen Basisklasse erben. Die meisten anderen Methoden, denen ich begegnet bin, um die Vererbung von der Basis-Error-Klasse zu vermeiden, sind am Ende (mindestens) genauso komplex und auf unterschiedliche Weise problematisch.
Geprüfte Ausnahmen sind eine Verbesserung, die sich meiner Meinung nach für einen besseren statischen Analyseeinblick in die Codebasis eignet. In einer Codebasis mit ausreichender Komplexität kann es leicht sein, zu übersehen, welche Ausnahmen von einer bestimmten Funktion ausgelöst werden.

Für diejenigen, die nach Kompilierungsfehlersicherheit in Typoskript suchen, können Sie meine ts-results- Bibliothek verwenden.

@vultix für mich ist die trennung leider nicht klar genug um das in eine teameinstellung zu bringen.

@vultix Der Ansatz Ihrer Bibliothek wurde oben besprochen und ist genau das Gegenteil von dem, was diese Funktion hier lösen soll.
Javascript hat bereits den Mechanismus zur Behandlung von Fehlern, er heißt Ausnahmen, aber Typoskript fehlt eine Möglichkeit, ihn zu beschreiben.

@nitzantomer Ich stimme voll und ganz zu, dass diese Funktion eine Notwendigkeit für Typoskript ist. Meine Bibliothek ist nichts weiter als ein vorübergehender Ersatz, bis diese Funktion hinzugefügt wird.

function* range(min: number, max: number): Iterable<number> throws TypeError, RangeError {

Ich weiß, dass ich ein bisschen zu spät zu diesem Spiel komme, aber das Folgende scheint eher Typoskripty-Syntax als Komma zu sein.

Iterable<number> throws TypeError | RangeError

Aber ich kann immer noch gut mit Komma umgehen. Ich wünschte nur, wir hätten das in der Sprache.
Die Hauptsache ist, dass ich es auf JSON.parse() haben möchte, weil viele meiner Kollegen zu vergessen scheinen, dass JSON.parse einen Fehler werfen kann und es viel Hin und Her ersparen würde Pull-Anfragen.

@WORMSS
Ich stimme vollkommen zu.
Mein Vorschlag enthielt die von Ihnen empfohlene Syntax:

function mightThrow(): void throws string | MyErrorType { ... }

Können wir dem Fehlerdeklarationsmuster einer OOP-Sprache wie Java folgen? Das Schlüsselwort „throws“ wird verwendet, um zu erklären, dass wir „try..catch“ ausführen müssen, wenn wir eine Funktion mit einem potenziellen Fehler verwenden

@allicanseenow
Der Vorschlag sieht die optionale Verwendung der throws-Klausel vor.
Das heißt, Sie müssen Try/Catch nicht verwenden, wenn Sie eine Funktion verwenden, die auslöst.

@allicanseenow Vielleicht möchten Sie meine obige Beschreibung für den Kontext lesen, einschließlich des verlinkten Artikels: https://github.com/microsoft/TypeScript/issues/13219#issuecomment -416001890

Ich denke, das ist eines der fehlenden Dinge im Typoskript-System.
Es ist rein optional, ändert den ausgegebenen Code nicht und hilft bei der Arbeit mit Bibliotheken und nativen Funktionen.

Das Wissen, welche Fehler möglicherweise ausgelöst werden, könnte es Editoren ermöglichen, die Fangklausel mit if-Bedingungen automatisch zu vervollständigen, um alle Fehler zu behandeln.

Ich meine, selbst kleine Anwendungen verdienen eine angemessene Fehlerbehandlung - das Schlimmste ist jetzt, dass Benutzer nicht wissen, wann etwas schief gehen könnte.

Für mich ist dies jetzt das TOP fehlende Feature.

@RyanCavanaugh , es gab eine Menge Feedback, seit das Label „Warten auf mehr Feedback“ hinzugefügt wurde. Können alle Mitglieder des Typoskript-Teams eingreifen?

@nitzantomer Ich unterstütze die Idee, TypeScript Throws hinzuzufügen, aber würde ein optionales Try/Catch nicht potenziell ungenaue Throws-Deklarationen zulassen, wenn es in verschachtelten Funktionen verwendet wird?

Damit ein Entwickler darauf vertrauen kann, dass die throws-Klausel einer Methode korrekt ist, müsste er die gefährliche Annahme treffen, dass zu keinem Zeitpunkt in der Aufrufhierarchie dieser Methode eine optionale Ausnahme ignoriert und ungemeldet auf den Stack geworfen wurde. Ich denke, @ajxs hat vielleicht am Ende seines Kommentars darauf angespielt, aber ich denke, das wäre ein großes Problem. Vor allem, wie fragmentiert die meisten npm-Bibliotheken sind.

@ConnorSinnott
Ich hoffe, dass ich dich richtig verstehe:

Der Compiler leitet ausgelöste Typen ab, zum Beispiel:

function fn() {
    if (something) {
        throw "something happened";
    }
}

Wird tatsächlich function fn(): throws string { ... } sein.
Der Compiler gibt auch einen Fehler aus, wenn die deklarierten Fehler nicht mit den tatsächlichen übereinstimmen:

function fn1() throws string | MyError {
    ...
}

function fn2() throws string {
    fn1(); // throws but not catched

    if (something) {
        throws "damn!";
    }
}

Der Compiler sollte sich darüber beschweren, dass fn2 string | MyError und nicht string $ wirft.

Vor diesem Hintergrund sehe ich nicht ein, dass diese Annahme gefährlicher ist als die Annahme, dass andere deklarierte Typen, denen ein Entwickler vertraut, wenn er andere Bibliotheken, Frameworks usw.
Ich bin mir nicht sicher, ob ich wirklich alle Optionen abgedeckt habe, und ich freue mich, wenn Sie sich ein interessantes Szenario ausdenken können.

Und selbst mit diesem Problem ist es sowieso so ziemlich dasselbe wie jetzt:

// can throw SyntaxError
declare function a_fancy_3rd_party_lib_function(): MyType;

function fn1() {
    if (something) {
        throws "damn!";
    }

    if (something else) {
        throws new Error("oops");
    }

    a_fancy_3rd_party_lib_function();
}

function fn2() {
    try {
        fn1();
    } catch(e) {
        if (typeof e === "string") { ... }
        else if (e instanceof MyError) { ... }
    }
}

Wir können uns auch davor durch Typskript schützen lassen, aber es erfordert eine Syntax, die sich ein wenig von Javascript unterscheidet:

function fn2() {
    try {
        fn1();
    } catch(typeof e === "string") {
        ...
    } catch(e instanceof MyError) {
        ...
    }
}

Dies wird kompiliert zu:

function fn2() {
    try {
        fn1();
    } catch(e) {
        if (typeof e === "string") {}
        else if (e instanceof MyError) {} 
        else {
            throw e;
        }
    }
}

Hallo! Danke für die Antwort! Tatsächlich mildert die Ableitung der Würfe, wie Sie erwähnt haben, dieses Problem, an das ich gedacht habe. Aber das zweite Beispiel, das Sie aufgelistet haben, ist interessant.

Gegeben

function first() : string throws MyVeryImportantError { // Compiler complains: missing number and string
    if(something) {
        throw MyVeryImportantError
    } else {
        return second().toString();
    }
}

function second() : number {
    if(something)
        throw 5;
    else   
        return third();
}

function third() : number throws string {
    if(!something)
        throw 'oh no!';
    return 9;
}

Um MyVeryImportantError explizit zu deklarieren, müsste ich auch alle zusätzlichen Fehler des Callstacks explizit deklarieren, was je nach Tiefe der Anwendung eine Handvoll sein könnte. Auch möglicherweise langweilig. Ich möchte sicherlich nicht die gesamte Anrufkette durchgehen, um eine Liste potenzieller Fehler zu erstellen, die auf dem Weg auftreten könnten, aber vielleicht könnte die IDE helfen.

Ich dachte daran, eine Art Spread-Operator vorzuschlagen, damit der Entwickler seinen Fehler explizit deklarieren und den Rest einfach hochwerfen kann.

function first() : string throws MyVeryImportantError | ... { // Spread the rest of the errors

Aber es gäbe einen einfacheren Weg, dasselbe Ergebnis zu erzielen: Lassen Sie einfach die throws-Deklaration fallen.

function first() : string { // Everything automatically inferred

Das wirft die Frage auf: Wann würde ich einen Vorteil darin sehen, das Schlüsselwort throws zu verwenden, anstatt nur Typoskript auf die Fehler schließen zu lassen?

@ConnorSinnott

Es unterscheidet sich nicht von anderen Sprachen, die die Throw-Klausel verwenden, dh Java.
Aber ich denke, dass man es in den meisten Fällen nicht wirklich mit vielen Typen zu tun haben wird. In der realen Welt haben Sie normalerweise nur mit string , Error (und Unterklassen) und verschiedenen Objekten ( {} ) zu tun.
Und in den meisten Fällen können Sie nur eine übergeordnete Klasse verwenden, die mehr als einen Typ erfasst:

type MyBaseError = {
    message: string;
};

type CodedError = MyBaseError & {
    code: number;
}

function fn1() throws MyBaseError {
    if (something) {
        throw { message: "something went wrong" };
    }
}

function fn2() throw MyBaseError {
    if (something) {
        throw { code: 2, message: "something went wrong" };
    }

    fn1();
}

Was die implizite Verwendung der Throw-Klausel betrifft, anstatt sie vom Compiler ableiten zu lassen, ist es meiner Meinung nach genau wie beim Deklarieren von Typen in Typoskript. In vielen Fällen müssen Sie dies nicht tun, aber Sie können dies tun, um Ihren Code besser zu dokumentieren damit wer es später liest, kann es besser verstehen.

Ein weiterer Ansatz, wenn Sie Typsicherheit wollen, besteht darin, Fehler einfach als Werte a la Golang zu behandeln:
https://gist.github.com/brandonkal/06c4a9c630369979c6038fa363ec6c83
Dennoch wäre dies ein nettes Feature zu haben.

@brandonkal
Dieser Ansatz wurde bereits früher im Thread diskutiert.
Es ist möglich, aber dabei wird ein inhärenter Teil / eine Funktion ignoriert, die uns Javascript ermöglicht.

Ich denke auch, dass der noexcept -Spezifizierer (#36075) derzeit die einfachste und beste Lösung ist, da für die meisten Programmierer das Auslösen von Ausnahmen ein Anti-Pattern ist.

Hier sind einige coole Funktionen:

  1. Beim Überprüfen von Funktionen wird kein Fehler ausgegeben:
noexcept function foo(): void {
  throw new Error('Some exception'); // <- compile error!
}
  1. Warnung vor überflüssigen try..catch Blöcken:
try { // <- warning!
  foo();
} catch (e) {}
  1. Resolve immer verspricht:
interface ResolvedPromise<T> extends Promise<T> {
  // catch listener will never be called
  catch(onrejected?: noexcept (reason: never) => never): ResolvedPromise<T>; 

  // same apply for `.then` rejected listener,
  // resolve listener should be with `noexpect`
}

@mosest

Wie bei den meisten Programmierern wird beim Auslösen von Ausnahmen ein Antimuster berücksichtigt.

Ich schätze, ich gehöre nicht zu dieser Gruppe der "meisten Programmierer".
Das noexcept ist cool und alles, aber darum geht es in dieser Ausgabe nicht.

Wenn die Werkzeuge da sind, werde ich sie benutzen. throw , try/catch und reject sind da, also werde ich sie verwenden.

Es wäre nur schön, sie richtig in Maschinenschrift getippt zu bekommen.

Wenn die Werkzeuge da sind, werde ich sie benutzen. throw , try/catch und reject sind da, also werde ich sie verwenden.

Sicher. Ich werde sie auch verwenden. Mein Punkt ist, dass noexcept oder throws never ein guter Anfang für diese Ausgabe sein werden.

Dummes Beispiel: Ich möchte wissen, dass, wenn ich Math.sqrt(-2) , es niemals einen Fehler auslöst.

Gleiches gilt für Bibliotheken von Drittanbietern. Es gibt mehr Kontrolle über den Code und die Grenzfälle, die ich als Programmierer handhaben muss.

@mosest
Aber wieso?
Dieser Vorschlag beinhaltet alle Vorteile des „Noexpect“-Vorschlags, also warum sich mit weniger zufrieden geben?

Weil es ewig dauert (diese Ausgabe ist 3 Jahre alt) und ich gehofft hatte, dass wir zumindest zuerst mit der grundlegendsten Funktion beginnen können.

@mosest
Nun, ich bevorzuge eine bessere Funktion, die länger dauert, als eine Teillösung, die weniger Zeit in Anspruch nimmt, aber wir bleiben für immer dabei.

Ich habe das Gefühl, dass noexcept später leicht hinzugefügt werden kann, tatsächlich könnte es ein separates Thema sein.

noexcept ist dasselbe wie throws never .

TBH Ich denke, es ist wichtiger, einen Mechanismus zu haben, der die Behandlung von Ausnahmen garantiert (auch wenn error in Go nicht verwendet wird), anstatt Typhinweise für try/catch

@roll es sollte keine "Garantie" geben.
Es ist kein Muss, Ausnahmen in Javascript zu behandeln, und es sollte auch kein Muss in Typoskript sein.

Es könnte eine Option im Rahmen des strikten Modus von Typescrypt sein, dass eine Funktion den Fehler entweder abfangen oder explizit throws deklarieren und weitergeben muss

Um meine 5 Cent hinzuzufügen: Ich sehe Ausnahmen, die einer Funktion ähneln, die null : Es ist eine Sache, die Sie normalerweise nicht erwarten. Aber wenn es passiert, tritt ein Laufzeitfehler auf, was schlecht ist. Jetzt hat TS "nicht-nullable-Typen" hinzugefügt, um Sie daran zu erinnern, mit null . Ich sehe das Hinzufügen throws als einen Schritt weiter, indem es Sie daran erinnert, auch Ausnahmen zu behandeln. Deshalb denke ich, dass dieses Feature unbedingt benötigt wird.

Wenn Sie sich Go ansehen, das Fehler zurückgibt, anstatt sie zu werfen, können Sie noch deutlicher sehen, dass beide Konzepte nicht so unterschiedlich sind. Außerdem hilft es Ihnen, einige APIs besser zu verstehen. Vielleicht wissen Sie nicht, dass einige Funktionen auslösen können, und Sie bemerken es viel später in der Produktion (z. B. JSON.parse , wer weiß, wie viele es noch gibt?).

@ obedm503 Dies ist eigentlich eine Philosophie von TS: Standardmäßig beschwert sich der Compiler über nichts. Sie können Optionen aktivieren, um bestimmte Dinge als Fehler zu behandeln, oder den strikten Modus aktivieren, um alle Optionen auf einmal zu aktivieren. Das sollte also gegeben sein.

Ich liebe diese vorgeschlagene Funktion.
Das Eingeben von Ausnahmen und Inferenzen kann eines der besten Dinge in der gesamten Programmiergeschichte sein.
❤️

Hallo, ich habe gerade ein wenig Zeit damit verbracht, eine Problemumgehung für das zu finden, was wir derzeit in TS haben. Da es keine Möglichkeit gibt, den Typ der geworfenen Fehler innerhalb eines Funktionsbereichs zu erhalten (und übrigens auch nicht den Typ der aktuellen Methode), habe ich herausgefunden, wie wir die erwarteten Fehler jedoch explizit innerhalb der Methode selbst festlegen können. Wir könnten diese Typen später abrufen und zumindest wissen, was innerhalb der Methode geworfen werden könnte.

Hier ist mein POC

/***********************************************
 ** The part to hide a type within another type
 **********************************************/
// A symbol to hide the type without colliding with another existing type
const extraType = Symbol("A property only there to store types");

type extraType<T> = {
    [extraType]?: T;
}

// Set an extra type to any other type
type extraTyped<T, E> = T & extraType<E>

// Get back this extra type
type getExtraType<T> = T extends extraType<infer T> ? T : never;

/***********************************************
 ** The part to implement a throwable logic
 **********************************************/

// Throwable is only a type holding the possible errors which can be thrown
type throwable<T, E extends Error> = extraTyped<T,E>

// return the error typed according to the throwableMethod passed into parameter
type basicFunction = (...any: any[]) => any;
const getTypedError = function<T extends basicFunction> (error, throwableMethod:T) {
    return error as getExtraType<ReturnType<T>>;
};

/***********************************************
 ** An example of usage
 **********************************************/

class CustomError extends Error {

}

// Here is my unreliable method which can crash throwing Error or CustomError.
// The returned type is simply our custom type with what we expect as the first argument and the
// possible thrown errors types as the second (in our case a type union of Error and CustomError)
function unreliableNumberGenerator(): throwable<number, Error | CustomError> {

    if (Math.random() > 0.5) {
        return 42;
    }

    if (Math.random() > 0.5) {
        new Error('No luck');
    }

    throw new CustomError('Really no luck')
}

// Usage
try {
    let myNumber = unreliableNumberGenerator();
    myNumber + 23;
}

// We cannot type error (see TS1196)
catch (error) {
    // Therefore we redeclare a typed value here and we must tell the method which could have crashed
    const typedError = getTypedError(error, unreliableNumberGenerator);

    // 2 possible usages:
    // Using if - else clauses
    if (typedError instanceof CustomError) {

    }

    if (typedError instanceof Error) {

    }

    // Or using a switch case on the constructor:
    // Note: it would have been really cool if TS did understood the typedError.constructor is narrowed by the types Error | CustomError
    switch (typedError.constructor) {
        case Error: ;
        case CustomError: ;
    }

}

// For now it is half a solution as the switch case is not narrowing anything. This would have been 
// possible if the typedError would have been a string union however it would not be reliable to rely
// on typedError.constructor.name (considering I would have find a way to convert the type union to a string union)

Vielen Dank für Ihr positives Feedback! Mir ist klar, dass es einfacher sein könnte, vorhandenen Code umzugestalten, ohne den gesamten zurückgegebenen Typ in den throwable -Typ umschließen zu müssen. Stattdessen könnten wir diesen einfach an den zurückgegebenen Typ anhängen. Mit dem folgenden Code können Sie die auswerfbaren Fehler wie folgt anhängen:

// now only append '& throwable<ErrorsThrown>' to the returned type
function unreliableNumberGenerator(): number & throwable<Error | CustomError> { /* code */ }

Dies ist die einzige Änderung für den Beispielteil, hier ist die neue Typendeklaration:

/***********************************************
 ** The part to hide a type within another type
 **********************************************/
// A symbol to hide the type without colliding with another existing type
const extraType = Symbol("A property only there to store types");

type extraType<T> = {
    [extraType]?: T;
}

// Get back this extra type
type getExtraType<T> = T extends extraType<infer T> ? T : never;

/***********************************************
 ** The part to implement a throwable logic
 **********************************************/

// Throwable is only a type holding the possible errors which can be thrown
type throwable<E extends Error> = extraType<E>

// return the error typed according to the throwableMethod passed into parameter
type basicFunction = (...any: any[]) => any;

type exceptionsOf<T extends basicFunction> = getExtraType<ReturnType<T>>;

const getTypedError = function<T extends basicFunction> (error: unknown, throwableMethod:T) {
    return error as exceptionsOf<T>;
};

Ich habe auch einen neuen Typ exceptionsOf hinzugefügt, der es erlaubt, die Fehler einer Funktion zu extrahieren, um die Verantwortung zu eskalieren. Zum Beispiel:

function anotherUnreliableNumberGenerator(): number & throwable<exceptionsOf<typeof unreliableNumberGenerator>> {
// I don't want to use a try and catch block here
    return (Math.random() > 0.5) ? unreliableNumberGenerator() : 100;
}

Da exceptionsOf eine Vereinigung von Fehlern erhält, können Sie so viele kritische Methoden eskalieren, wie Sie möchten:

function aSuperUnreliableNumberGenerator(): number & throwable<exceptionsOf<typeof unreliableNumberGenerator> | exceptionsOf<typeof anotherUnreliableNumberGenerator>> {
// I don't want to use a try and catch block here
    return (Math.random() > 0.5) ? unreliableNumberGenerator() : unreliableNumberGenerator();
}

Ich mag die Verwendung von typeof nicht, wenn ich einen besseren Weg finde, werde ich es dich wissen lassen

Sie können hier die Ergebnistipps testen : Bewegen Sie den Mauszeiger typedError in Zeile 106

@Xample Das ist eine großartige Lösung mit den Tools, die wir jetzt haben!
Aber ich denke, dass es in der Praxis nicht ausreicht, da Sie immer noch Dinge tun können wie:

const a = superRiskyMethod();
const b = a + 1;

Und der Typ von b wird als Zahl abgeleitet, die korrekt ist, aber nur, wenn sie in einen Versuch eingeschlossen ist
Dies sollte nicht gültig sein

@luisgurmendezMLabs Ich verstehe nicht ganz, was du meinst. Wenn Sie dem Repo von @Xample in Zeile 56 folgen, können Sie sehen, dass das Ergebnis von myNumber + 23 als number abgeleitet wird, während myNumber: number & extraType<Error | CustomError> .

In einer anderen Phase wird a in Ihrem Beispiel niemals in so etwas wie eine Try-Monade verpackt. Es hat überhaupt keinen Wrapper außer einer Schnittmenge mit & extraType<Error | CustomError> .

Glückwunsch zum tollen Design @Xample 👏👏👏👏👏👏. Das ist wirklich vielversprechend und auch ohne syntaktischen Zucker bereits nützlich. Haben Sie vor, dafür eine Typbibliothek zu erstellen?

@ivawzh Meine Gedanken sind, dass dies in der Praxis einige Probleme mit sich bringen könnte, da:

function stillARiskyMethod() { 
    const a = superRiskyMethod();
    return a + 1
}

Diese Rückgabe des Funktionstyps wird als Zahl abgeleitet, und das ist nicht ganz richtig

@luisgurmendezMLabs Der Throwable-Typ befindet sich nur im Rückgabetyp der Funktion, um den Fehlertyp mit der Funktion zu verbinden (und diese Typen später wiederherstellen zu können). Ich gebe niemals echte Fehler zurück, ich werfe sie nur. Wenn Sie sich also innerhalb eines Try-Blocks befinden oder nicht, ändert sich nichts.

@ivawzh danke der Nachfrage, ich habe es gerade für dich getan

@luisgurmendezMLabs ah ok, ich verstehe deinen Punkt, es scheint, dass Typoskript nur auf den ersten gefundenen Typ schließen lässt. Wenn Sie zum Beispiel Folgendes hatten:

function stillARiskyMethod() { 
    return superRiskyMethod();
}

der Rückgabetyp von stillARiskyMethod würde korrekt abgeleitet werden, while

function stillARiskyMethod() { 
    return Math.random() < 0.5 superRiskyMethod() : anotherSuperRiskyMethod();
}

würde nur den Rückgabetyp als superRiskyMethod() eingeben und den von anotherSuperRiskyMethod() verwerfen. Mehr habe ich nicht recherchiert

Aus diesem Grund müssen Sie den Fehlertyp manuell eskalieren.

function stillARiskyMethod(): number & throwable<exceptionOf<typeof superRiskyMethod>> { 
    const a = superRiskyMethod();
    return a + 1
}

Ich wollte auch nur meine Gedanken dazu abgeben.

Ich denke, dies wäre eine wirklich gute Funktion, die implementiert werden sollte, da es verschiedene Anwendungsfälle gibt, um einen Fehler/Null zurückzugeben und etwas zu werfen, und es könnte viel Potenzial haben. Das Auslösen von Ausnahmen ist sowieso Teil der Javascript-Sprache, also warum sollten wir nicht die Möglichkeit geben, diese einzugeben und abzuleiten?

Wenn ich beispielsweise viele Aufgaben erledige, bei denen Fehler auftreten könnten, fände ich es unpraktisch, jedes Mal eine if-Anweisung verwenden zu müssen, um den Rückgabetyp zu überprüfen, wenn dies durch die Verwendung eines try/catch-Befehls vereinfacht werden könnte, wo, falls vorhanden Wenn eine dieser Aufgaben ausgelöst wird, wird sie ohne zusätzlichen Code im Catch behandelt.

Dies ist besonders nützlich, wenn Sie die Fehler auf die gleiche Weise behandeln ; Zum Beispiel möchte ich in express/node.js den Fehler an NextFunction (Error Handler) übergeben.

Anstatt jedes Mal if (result instanceof Error) { next(result); } zu machen, könnte ich einfach den gesamten Code für diese Aufgaben in einen try/catch verpacken, und in meinem Catch weiß ich, dass ich dies immer an meine weitergeben möchte, da eine Ausnahme ausgelöst wurde Fehlerbehandler, so kann catch(error) { next(error); }

Ich habe diese Diskussion noch nicht gesehen (vielleicht habe ich sie aber übersehen, dieser Thread hat einige Kommentare!), aber wenn dies implementiert wäre, wäre es obligatorisch (dh: Kompilierungsfehler), eine Funktion zu haben, die ohne Verwendung von wirft throws -Klausel in seiner Funktionsdeklaration? Ich denke, dass dies nett wäre (Wir zwingen die Leute nicht, die Throws zu handhaben, es würde sie nur darüber informieren, dass die Funktion Throws macht) , aber die große Sorge hier ist, dass, wenn Typescript auf diese Weise aktualisiert würde, dies wahrscheinlich der Fall wäre viele derzeit vorhandene Codes brechen.

Bearbeiten: Ein weiterer Anwendungsfall, an den ich dachte, könnte sein, dass dies auch bei der Generierung von JSDocs mit dem @throws -Tag helfen würde

Ich werde hier wiederholen, was ich in der jetzt begangenen Ausgabe gesagt habe.


Ich denke, dass Typoskript in der Lage sein sollte, den Fehlertyp der meisten JavaScript-Ausdrücke abzuleiten. Ermöglicht eine schnellere Implementierung durch Bibliotheksersteller.

function a() {
  if (Math.random() > .5) {
    throw 'unlucky';
  }
}

function b() {
  a();
}

function c() {
  if (Math.random() > .5) {
    throw 'unlucky';
  }
  if (Math.random() > .5) {
    throw 'fairly lucky';
  }
}

function d() {
  return eval('You have no IDEA what I am capable of!');
}

function e() {
  try {
    return c;
  } catch(e) {
    throw 'too bad...';
  }
}

function f() {
  c();
}

function g() {
  a();
  c();
}
  • Funktion a Wir wissen, dass der Fehlertyp 'unlucky' ist, und wenn wir sehr vorsichtig sein wollen, können wir ihn auf Error | 'unlucky' erweitern, also includeError .
  • Die Funktion b erbt den Fehlertyp der Funktion a .
  • Funktion c ist fast identisch; 'unlucky' | 'fairly lucky' oder Error | 'unlucky' | 'fairly lucky' .
  • Die Funktion d muss unknown werfen, da eval ... unbekannt ist
  • Die Funktion e fängt den Fehler von d ab, aber da es throw im catch-Block gibt, leiten wir hier seinen Typ 'too bad...' ab, da der Block nur enthält throw 'primitive value' wir könnten sagen, es kann Error nicht werfen (Korrigieren Sie mich, wenn ich etwas schwarze JS-Magie verpasst habe ...)
  • Die Funktion f erbt von c genauso wie b von a .
  • Die Funktion g erbt 'unlucky' von a und unknown von c , also 'unlucky' | unknown => unknown

Hier sind einige Compiler-Optionen, die meiner Meinung nach enthalten sein sollten, da die Interaktion der Benutzer mit dieser Funktion je nach ihren Fähigkeiten sowie der Typsicherheit der Bibliotheken, auf die sie in einem bestimmten Projekt angewiesen sind, variieren kann:

{
  "compilerOptions": {
    "errorHandelig": {
      "enable": true,
      "forceCatching": false,   // Maybe better suited for TSLint/ESLint...
      "includeError": true, // If true, every function will by default throw `Error | (types of throw expressions)`
      "catchUnknownOnly": false, // If true, every function will by default throw `unknown`, for those very skeptics (If someone replaces some global with weird proxy, for example...)
      "errorAsesertion": true,  // If false, the user will not be able to change error type manually
    }
  }
}

Was die Syntax zum Ausdrücken des Fehlers betrifft, den jede Funktion erzeugen kann, bin ich mir nicht sicher, aber ich weiß, dass wir die Fähigkeit brauchen, damit sie generisch und ableitbar ist.

declare function getValue<T extends {}, K extends keyof T>(obj: T, key: K): T[K] throws void;
declare function readFile<throws E = 'not valid file'>(file: string, customError: E): string throws E;

Mein Anwendungsfall, da das Zeigen eines tatsächlichen Anwendungsfalls anderen zeigen könnte, dass er einen Wert hat:

declare function query<T extends {}, throws E>(sql: string, error: E): T[] throws E;

app.get('path',(req, res) => {
  let user: User;
  try {
    user = query('SELECT * FROM ...', 'get user');
  } catch(e) {
    return res.status(401);
  }

  try {
    const [posts, followers] = Promise.all([
      query('SELECT * FROM ...', "user's posts"),
      query('SELECT * FROM ...', "user's follower"'),
    ]);

    res.send({ posts, followers });
  } catch(e) {
    switch (e) {
      case "user's posts":
        return res.status(500).send('Loading of user posts failed');

      case "user's posts":
        return res.status(500).send('Loading of user stalkers failed, thankfully...');

      default:
        return res.status(500).send('Very strange error!');
    }
  }
});

Ich brauche eine Fehlersenke, um zu verhindern, dass Antwortheader für mehrere Fehler mehrmals gesendet werden (normalerweise treten sie nicht auf, aber wenn sie dies tun, tun sie dies in großen Mengen!)

Dieses Problem ist immer noch mit Awaiting More Feedback . Was können wir tun, um mehr Feedback zu geben? Dies ist das einzige Feature, um das ich die Java-Sprache beneide

Wir haben einen Anwendungsfall, bei dem wir einen bestimmten Fehler auslösen, wenn ein API-Aufruf Nicht-200-Code zurückgibt:

interface HttpError extends Error {
  response: Response
}

try {
  loadData()
} catch (error: Error | ResponseError) {
  if (error.response) {
    checkStatusCodeAndDoSomethingElse()
  } else {
    doGenericErrorHandling()
  }
}

Wenn der catch-Block nicht eingegeben werden kann, vergessen die Entwickler, dass zwei mögliche Arten von Fehlern ausgegeben werden können und sie beide behandeln müssen.

Ich ziehe es vor, immer ein Error-Objekt zu werfen:

function fn(num: number): void {
    if (num === 0) {
        throw new TypeError("Can't deal with 0")
    }
}
try {
 fn(0)
}
catch (err) {
  if (err instanceof TypeError) {
   if (err.message.includes('with 0')) { .....}
  }
}

Warum zeigt diese Funktion immer noch "nicht genug Feedback"? Es ist so nützlich, wenn Sie die API eines Browsers wie indexedDB oder localstorage aufrufen. Es hat viele Fehler in komplexen Szenarien verursacht, aber der Entwickler kann sich dessen bei der Programmierung nicht bewusst sein.

Hegel scheint diese Eigenschaft perfekt zu haben.
https://hegel.js.org/docs#benefits (scrollen Sie zum Abschnitt „Typed Error“)

Ich wünschte, TypeScript hätte eine ähnliche Funktion!

DL;DR;

  • Die Reject-Funktion der Promises sollte typisiert werden
  • Jeder Typ, der in einen try -Block geworfen wird, sollte unter Verwendung einer Union in das Fehlerargument von catch abgeleitet werden
  • error.constructor sollte korrekt mit dem realen Typ und nicht nur any typisiert werden, um zu verhindern, dass ein ausgelöster Fehler übersehen wird.

Okay, vielleicht sollten wir einfach klarstellen, was unsere Bedürfnisse und Erwartungen sind:

Fehler werden in js normalerweise auf drei Arten behandelt

1. Der Knotenweg

Verwendung von Rückrufen (die tatsächlich eingegeben werden können)

Anwendungsbeispiel:

import * as fs from 'fs';

fs.readFile('readme.txt', 'utf8',(error, data) => {
    if (error){
        console.error(error);
    }
    if (data){
        console.log(data)
    }
});

Wo fs.d.ts uns gibt:

function readFile(path: PathLike | number, options: { encoding: string; flag?: string; } | string, callback: (err: NodeJS.ErrnoException | null, data: string) => void): void;

Daher wird der Fehler so typisiert

    interface ErrnoException extends Error {
        errno?: number;
        code?: string;
        path?: string;
        syscall?: string;
        stack?: string;
    }

2. Der Verheißungsweg

Wo das Versprechen entweder gelöst oder abgelehnt wird, können Sie zwar das aufgelöste value eingeben, aber nicht die Ablehnung, die oft als reason bezeichnet wird.

Hier ist die Signatur des Konstruktors eines Promise: Beachten Sie, dass der Grund any ist

    new <T>(executor: (resolve: (value?: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void): Promise<T>;

Es hätte theoretisch möglich sein, sie wie folgt zu tippen:

    new <T, U=any>(executor: (resolve: (value?: T | PromiseLike<T>) => void, reject: (reason?: U) => void) => void): Promise<T>;

Auf diese Weise könnten wir theoretisch immer noch eine 1-1-Konvertierung zwischen einem Node-Callback und einem Promise vornehmen und dabei die gesamte Eingabe während dieses Prozesses beibehalten. Zum Beispiel:

const fs = require('fs')
const util = require('util')

const readFilePromise = util.promisify(fs.readFile); // (path: PathLike | number, options: { encoding: string; flag?: string; } | string) => Promise<data: string, NodeJS.ErrnoException>;

3. Der „Try and Catch“-Weg

try {
    throw new Error();
}
catch (error: unknown) { //typing the error is possible since ts 4.0
    if (error instanceof Error) {
        error.message;
    }
}

Obwohl wir einen Fehler "Error" 2 Zeilen vor dem Catch-Block ausgeben, kann TS error (Wert) nicht als Error (Typ) eingeben.

Ist es nicht der Fall, Versprechungen innerhalb einer async -Funktion zu verwenden (es gibt "noch" keine Zauberei). Verwenden unseres versprochenen Node-Callbacks:

async function Example() {
    try {
        const data: string = await readFilePromise('readme.txt', 'utf-8');
        console.log(data)
    }
    catch (error) { // it can only be NodeJS.ErrnoException, we can type is ourself… but nothing prevents us to make a mistake
        console.error(error.message);
    }
}

Es fehlen keine Informationen für Typescript, um vorhersagen zu können, welche Art von Fehler in einem Try-Bereich ausgelöst werden könnte.
Wir sollten jedoch berücksichtigen, dass interne, native Funktionen Fehler auslösen können, die nicht im Quellcode enthalten sind. Wenn jedoch jedes Mal, wenn ein „throw“-Schlüsselwort in der Quelle enthalten ist, sollte TS den Typ sammeln und als möglichen Typ für den Fehler vorschlagen. Diese Typen würden natürlich durch den Block try begrenzt.

Dies ist nur der erste Schritt und es wird noch Raum für Verbesserungen geben, wie z. B. einen strikten Modus, der TS dazu zwingt, wie in Java zu arbeiten, dh den Benutzer zu zwingen, eine riskante Methode (eine Methode, die etwas werfen kann) innerhalb von try zu verwenden function example throw ErrorType { ... } markieren, um die Verantwortung für die Behandlung der Fehler zu eskalieren.

Last but not least: Verhindern Sie, dass ein Fehler übersehen wird

Alles kann geworfen werden, nicht nur ein Fehler oder sogar eine Instanz eines Objekts. Das bedeutet, dass Folgendes gilt

try {
    if (Math.random() > 0.5) {
        throw 0
    } else {
        throw new Error()
    }
}
catch (error) { // error can be a number or an object of type Error
    if (typeof error === "number") {
        alert("silly number")
    }

    if (error instanceof Error) {
        alert("error")
    }
}

Zu wissen, dass der Fehler vom Typ number | Error sein könnte, wäre unglaublich hilfreich. Um jedoch zu verhindern, dass die Behandlung eines möglichen Fehlertyps vergessen wird, ist es nicht wirklich die beste Idee, separate if / else-Blöcke ohne einen strengen Satz möglicher Ergebnisse zu verwenden.
Ein Switch-Fall würde dies jedoch viel besser machen, da wir gewarnt werden können, wenn wir vergessen haben, einen bestimmten Fall abzugleichen (der auf die Standardklausel zurückgreifen würde). Wir können (es sei denn, wir tun etwas Hackisches) die Groß-/Kleinschreibung eines Objektinstanztyps ändern, und selbst wenn wir könnten, können wir alles werfen (nicht nur ein Objekt, sondern auch einen booleschen Wert, eine Zeichenfolge und eine Zahl, die keine "Instanz" haben). Wir können jedoch den Konstruktor der Instanz verwenden, um herauszufinden, um welchen Typ es sich handelt. Wir können den obigen Code nun wie folgt umschreiben:

try {
    if (Math.random() > 0.5) {
        throw 0
    } else {
        throw new Error()
    }
}
catch (error) { // error can be a Number or an object of type `Error`
    switch (error.constructor){
        case Number: alert("silly number"); break;
        case Error: alert("error"); break;
    }
}

Hurra… das einzige verbleibende Problem ist, dass TS die error.constructor nicht tippt und es daher (noch?) js.

Bitte kommentieren Sie, wenn Sie mehr Feedback benötigen

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen