Typescript: Предложение: предложение `throws` и введенное предложение catch

Созданный на 29 дек. 2016  ·  135Комментарии  ·  Источник: microsoft/TypeScript

Система типов typescript полезна в большинстве случаев, но ее нельзя использовать при обработке исключений.
Например:

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

Проблема здесь двоякая (не просматривая код):

  1. При использовании этой функции нет способа узнать, что она может вызвать ошибку
  2. Неясно, какой тип (ы) ошибки будет

Во многих сценариях это на самом деле не проблема, но знание того, может ли функция/метод вызвать исключение, может быть очень полезным в разных сценариях, особенно при использовании разных библиотек.

Путем введения (необязательного) проверенного исключения система типов может использоваться для обработки исключений.
Я знаю, что проверенные исключения не согласованы (например, Андерс Хейлсберг ), но, сделав их необязательными (и, возможно, выведенными? Подробнее позже), это просто добавляет возможность добавить больше информации о коде, который может помочь разработчикам, инструментам и документация.
Это также позволит лучше использовать значимые пользовательские ошибки для больших больших проектов.

Поскольку все ошибки времени выполнения javascript имеют тип Error (или расширяющие типы, такие как TypeError ), фактический тип функции всегда будет type | Error .

Грамматика проста, определение функции может заканчиваться предложением throws, за которым следует тип:

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

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

При перехвате исключений синтаксис такой же, как и возможность объявить тип(ы) ошибки:
catch(e: string | Error) { ... }

Примеры:

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

Здесь ясно, что функция может выдать ошибку и что ошибка будет строкой, поэтому при вызове этого метода разработчик (и компилятор/IDE) знает об этом и может лучше с этим справиться.
Так:

fn(0);

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

Компилируется без ошибок, но:

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

Не удается скомпилировать, поскольку number не является string .

Вывод потока управления и типа ошибки

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";
    }
}

Выдает string .

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

    fn(num);
}

Выдает MyError | string .
Тем не мение:

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

Выдает только MyError .

Awaiting More Feedback Suggestion

Самый полезный комментарий

@aleksey-bykov

Вы предлагаете вообще не использовать throw в моем коде и вместо этого обернуть результаты (в функциях, которые могут привести к ошибке).
У этого подхода есть несколько недостатков:

  • Эта упаковка создает больше кода
  • Это требует, чтобы вся цепочка вызванных функций возвращала это обернутое значение (или ошибку), или, альтернативно, функция, которая получает Tried<> , не может игнорировать ошибку.
  • Это не стандарт, сторонние библиотеки и собственные ошибки js.

Добавление throws позволит разработчикам, которые решат обрабатывать ошибки из своего кода, сторонних библиотек и собственного js.
Поскольку в предложении также содержится запрос на вывод ошибок, все сгенерированные файлы определений могут включать предложение throws .
Будет очень удобно узнать, какие ошибки функция может выдать прямо из файла определения, а не из текущего состояния, когда вам нужно перейти к документам, например, чтобы узнать, какую ошибку JSON.parse может выдать мне нужно идти на страницу MDN и прочитайте это:

Выдает исключение SyntaxError , если строка для анализа недействительна JSON

И это хороший случай, когда ошибка задокументирована.

Все 135 Комментарий

Просто уточню: одна из идей здесь состоит не в том, чтобы заставить пользователей перехватывать исключение, а в том, чтобы лучше определить тип переменной предложения catch?

@ДаниэльРозенвассер
Да, пользователи не будут вынуждены перехватывать исключения, так что с компилятором все в порядке (конечно, во время выполнения выдается ошибка):

function fn() {
    throw "error";
}

fn();

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

Но это даст разработчикам способ выразить, какие исключения могут быть выброшены (было бы здорово иметь это при использовании файлов других библиотек .d.ts ), а затем сделать так, чтобы тип компилятора защищал типы исключений внутри предложения catch.

чем проверенный бросок отличается от 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);
}

вместо

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

@aleksey-bykov

Вы предлагаете вообще не использовать throw в моем коде и вместо этого обернуть результаты (в функциях, которые могут привести к ошибке).
У этого подхода есть несколько недостатков:

  • Эта упаковка создает больше кода
  • Это требует, чтобы вся цепочка вызванных функций возвращала это обернутое значение (или ошибку), или, альтернативно, функция, которая получает Tried<> , не может игнорировать ошибку.
  • Это не стандарт, сторонние библиотеки и собственные ошибки js.

Добавление throws позволит разработчикам, которые решат обрабатывать ошибки из своего кода, сторонних библиотек и собственного js.
Поскольку в предложении также содержится запрос на вывод ошибок, все сгенерированные файлы определений могут включать предложение throws .
Будет очень удобно узнать, какие ошибки функция может выдать прямо из файла определения, а не из текущего состояния, когда вам нужно перейти к документам, например, чтобы узнать, какую ошибку JSON.parse может выдать мне нужно идти на страницу MDN и прочитайте это:

Выдает исключение SyntaxError , если строка для анализа недействительна JSON

И это хороший случай, когда ошибка задокументирована.

И это хороший случай, когда ошибка задокументирована.

есть ли надежный способ в javascript отличить SyntaxError от Error?

  • да, это больше кода, но поскольку плохая ситуация представлена ​​​​в объекте, его можно передавать для обработки, отбрасывания, сохранения или преобразования в действительный результат, как и любое другое значение.

  • вы можете игнорировать try, вернув также try, try можно рассматривать как монаду, ищите монадические вычисления

    function mightFail(): Tried<number, string> {
    }
    function mightFailToo(): Tried<number, string> {
        const tried = mightFail();
        if (isSuccess(tried))  { 
             return successFrom(tried.result * 2);
        } else {
             return tried;
        }
    }
    
  • это достаточно стандартно для вашего кода, когда дело доходит до сторонних библиотек, выбрасывающих исключение, это обычно означает завершение игры для вас, потому что почти невозможно надежно восстановиться после исключения, причина в том, что оно может быть выброшено из любого места внутри кода завершая его в произвольной позиции и оставляя его внутреннее состояние неполным или поврежденным

  • нет поддержки проверенных исключений из среды выполнения JavaScript, и я боюсь, что это невозможно реализовать только на машинописном языке

кроме того, что кодирование исключения в качестве особого случая результата является очень распространенной практикой в ​​​​мире FP

тогда как разделение возможного результата на 2 части:

  • один, доставленный оператором return и
  • другой доставлен броском

выглядит выдуманной трудностью

по моему мнению, throw хорош для быстрых и громких сбоев, когда вы ничего не можете с этим поделать, явно закодированные результаты хороши для всего, что подразумевает плохую, но ожидаемую ситуацию, из которой вы можете выйти.

учитывать:

// 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

Моя точка зрения на то, что JSON.parse может генерировать SyntaxError , заключается в том, что мне нужно найти функцию в документации, чтобы знать, что она может генерировать, и было бы легче увидеть это в .d.ts .
И да, вы можете знать, что это SyntaxError с использованием instanceof .

Вы можете изобразить такую ​​же плохую ситуацию с выбрасыванием ошибки.
Вы можете создать свой собственный класс ошибок, расширяющий Error , и поместить в него все необходимые данные.
Вы получаете то же самое с меньшим количеством кода.

Иногда у вас есть длинная цепочка вызовов функций, и вы можете захотеть обработать некоторые ошибки на разных уровнях цепочки.
Будет довольно неприятно всегда использовать обернутые результаты (монады).
Не говоря уже о том, что в любом случае могут быть выброшены другие библиотеки и нативные ошибки, поэтому вы можете в конечном итоге использовать как монады, так и try/catch.

Я не согласен с вами, во многих случаях вы можете исправить допущенные ошибки, и если язык позволяет вам выразить это лучше, это будет проще сделать.

Как и во многих вещах в typescript, отсутствие поддержки этой функции в javascript не является проблемой.
Этот:

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

Будет работать, как и ожидалось, в javascript, только без аннотации типа.

Использование throw достаточно, чтобы выразить то, что вы говорите: если операция прошла успешно, верните значение, иначе выдайте ошибку.
Затем пользователь этой функции решит, хочет ли он иметь дело с возможными ошибками или игнорировать их.
Вы можете иметь дело только с ошибками, которые вы сделали сами, и игнорировать, например, те, которые являются сторонними.

если мы говорим о браузерах, instanceof подходит только для вещей, происходящих из одного и того же окна/документа, попробуйте:

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

поэтому, когда вы делаете:

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

ты не поймаешь

еще одна проблема с исключениями, которые могут проникнуть в ваш код далеко за пределы того места, где вы ожидаете их появления.

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

кроме того, instanceof уязвим к наследованию прототипов, поэтому вам нужно быть особенно осторожным, чтобы всегда сверяться с окончательным предком

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

@aleksey-bykov Явная обработка ошибок, как вы предлагаете, в монадических структурах - довольно сложная и сложная задача. Это требует больших усилий, делает код трудным для понимания и требует языковой поддержки/испускания, управляемого типами, чтобы быть на грани терпимости. Этот комментарий исходит от человека, который прилагает много усилий для популяризации Haskell и FP в целом.

Это рабочая альтернатива, особенно для энтузиастов (включая меня), однако я не думаю, что это жизнеспособный вариант для более широкой аудитории.

На самом деле, меня больше всего беспокоит то, что люди начнут создавать подклассы Error. Я думаю, что это ужасный образец. В более общем смысле все, что способствует использованию оператора instanceof, просто создаст дополнительную путаницу в отношении классов.

Этот комментарий исходит от человека, который прилагает много усилий для популяризации Haskell и FP в целом.

я действительно думаю, что это должно быть усилено для аудитории, не раньше, чем она будет переварена и не будет запрошено больше, можем ли мы улучшить поддержку FP на языке

и это не так сложно, как вы думаете, при условии, что все комбинаторы уже написаны, просто используйте их для построения потока данных, как мы делаем в нашем проекте, но я согласен, что TS мог бы поддерживать это лучше: #2319

Трансформеры Monad — это настоящая PITA. Вам нужно довольно часто поднимать, поднимать и выборочно бегать. Конечным результатом является малопонятный код и гораздо более высокий, чем необходимо, порог входа. Все комбинаторы и лифтинг-функции (обеспечивающие обязательную упаковку/распаковку) — это лишь шум, отвлекающий вас от решаемой задачи. Я действительно считаю, что явно указывать состояние, эффекты и т. д. — это хорошо, но я не думаю, что мы еще нашли удобную оболочку/абстракцию. Пока мы не найдем его, поддержка традиционных шаблонов программирования кажется правильным путем, не прекращая пока экспериментировать и исследовать.

PS: я думаю, что нам нужно больше, чем пользовательские операторы. Высшие родственные типы и некоторые классы типов также важны для практической монадической библиотеки. Среди них я бы поставил HKT на первое место, а типографские классы — на второе место. Учитывая все вышесказанное, я считаю, что TypeScript не является языком для отработки таких концепций. Играть - да, но его философия и корни принципиально далеки от правильной бесшовной интеграции.

Вернемся к вопросу OP - instanceof - опасный оператор для использования. Однако явные исключения не ограничиваются Error . Вы также можете создавать свои собственные ADT или пользовательские ошибки POJO. Предложенная функция может быть весьма полезной и, конечно же, ею тоже можно довольно сильно злоупотреблять. В любом случае это делает функции более прозрачными, что, несомненно, хорошо. В целом я 50/50 на него :)

@aleksey-bykov

Разработчики должны знать о различных проблемах js, которые вы описали, ведь добавление throws к машинописному тексту не вносит ничего нового в js, он только дает машинописному тексту как языку возможность выражать существующее поведение js.

Тот факт, что сторонние библиотеки выдают ошибки, - это как раз моя точка зрения.
Если бы их файлы определений включали это, тогда у меня был бы способ это узнать.

@aluanhaddad
Почему это ужасный шаблон для расширения Error ?

@gcnew
Что касается instanceof , это был просто пример, я всегда могу выбрасывать обычные объекты, которые имеют разные типы, а затем использовать защиту типов, чтобы различать их.
Разработчик должен решить, какие типы ошибок он хочет выдавать, и, вероятно, это уже так, но в настоящее время нет способа выразить это, и это то, что это предложение хочет решить.

@nitzantomer Подклассы собственных классов ( Error , Array , RegExp и т. д.) не поддерживались в более старых версиях ECMAScript (до ES6). Низкий уровень эмиссии для этих классов дает неожиданные результаты (делаются все возможное, но это все, что можно сделать) и является причиной многочисленных проблем, регистрируемых ежедневно. Как правило, не делайте подклассы нативных, если вы не ориентируетесь на последние версии ECMAScript и действительно не знаете, что делаете.

@gcnew
О, я прекрасно знаю об этом, так как потратил более нескольких часов, пытаясь понять, что пошло не так.
Но с возможностью сделать это сейчас не должно быть причин не делать этого (при нацеливании на es6).

В любом случае это предложение не предполагает, что пользователь является подклассом класса Error, это был просто пример.

@nitzantomer Я не спорю, что предложение ограничено Error . Я только что объяснил, почему это плохой шаблон для подкласса. В своем посте я на самом деле защищал позицию, согласно которой также могут использоваться пользовательские объекты или размеченные объединения.

instanceof опасен и считается анти-шаблоном, даже если вы уберете особенности JavaScript - например , остерегайтесь оператора instanceof . Причина в том, что компилятор не может защитить вас от ошибок, вносимых новыми подклассами. Логика, использующая instanceof , является хрупкой и не следует принципу открытости/закрытости , так как она предполагает лишь несколько вариантов. Даже если добавлен случай подстановки, новые производные по-прежнему могут вызывать ошибки, поскольку они могут нарушать предположения, сделанные во время написания.

Для случаев, когда вы хотите различать известные альтернативы, в TypeScript есть тегированные объединения (также называемые размеченными объединениями или алгебраическими типами данных). Компилятор следит за тем, чтобы все случаи были обработаны, что дает вам хорошие гарантии. Недостатком является то, что если вы хотите добавить новую запись в тип, вам придется пройти весь код, различающий ее, и обработать только что добавленную опцию. Положительным моментом является то, что такой код, скорее всего, был бы сломан, но не работал во время выполнения.

Я просто еще раз подумал над этим предложением и стал против. Причина в том, что если объявления throws присутствовали в подписях, но не применялись принудительно, они уже могут быть обработаны комментариями к документации. В случае принудительного применения я разделяю мнение, что они станут раздражающими и быстро проглотят, поскольку в JavaScript отсутствует механизм Java для типизированных предложений catch. Использование исключений (особенно в качестве потока управления) также никогда не было устоявшейся практикой. Все это приводит меня к пониманию того, что проверенные исключения приносят слишком мало, в то время как доступны лучшие и в настоящее время более распространенные способы представления отказа (например, возврат объединения).

@gcnew
Вот как это делается в С#, проблема в том, что документы не являются стандартными в машинописном тексте.
Я не помню, чтобы мне попадался хорошо документированный файл определения. Различные файлы lib.d.ts содержат комментарии, но они не содержат выброшенных ошибок (с одним исключением: lib.es6.d.ts имеет один throws в Date[Symbol.toPrimitive](hint: string) ).

Кроме того, это предложение учитывает вывод ошибок, чего не произойдет, если ошибки исходят из комментариев к документации. С выводимыми проверенными исключениями разработчику даже не нужно будет указывать предложение throws , компилятор выведет его автоматически, будет использовать его для компиляции и добавит в результирующий файл определения.

Я согласен с тем, что принудительная обработка ошибок — это нехорошо, но наличие этой функции просто добавит больше информации, которую затем смогут использовать те, кто пожелает.
Проблема с:

... есть лучшие и в настоящее время более распространенные способы представления отказа

Разве что нет стандартного способа сделать это.
Вы можете использовать union return, @aleksey-bykov будет использовать Tried<> , а разработчик другой сторонней библиотеки сделает что-то совершенно другое.
Выдача ошибок является стандартом для разных языков (js, java, c#...), и поскольку это часть системы, а не обходной путь, она должна (на мой взгляд) лучше обрабатываться в машинописном тексте, и доказательством этого является число проблем, которые я видел здесь с течением времени, которые запрашивают аннотацию типа в предложении catch .

Я хотел бы иметь информацию во всплывающей подсказке в VS, если функция (или вызываемая функция) может генерировать. Для файлов *.d.ts нам, вероятно, нужен поддельный параметр, начиная с TS2.0.

@HolgerJeromin
Зачем это нужно?

вот простой вопрос, какая подпись должна быть выведена для dontCare в приведенном ниже коде?

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

function dontCare() {
   return mightThrow();
}

согласно тому, что вы сказали в своем предложении, это должно быть

function dontCare(): void throws string {

я говорю, что это должна быть ошибка типа, поскольку проверенное исключение не было должным образом обработано

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

это почему?

потому что в противном случае есть очень хороший шанс испортить состояние непосредственного вызывающего объекта:

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

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

если вы позволите исключению проскользнуть, вы не сможете вывести его как проверенное, потому что таким образом будет нарушен контракт поведения keepAllValues (не все значения были сохранены, несмотря на первоначальное намерение)

единственный безопасный способ - немедленно поймать их и явно перебросить

    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;
                }
           }
    }

в противном случае, несмотря на то, что вызывающие абоненты знают, что может быть брошено, вы не можете дать им гарантии, что безопасно продолжать использовать код, который только что бросил

поэтому не существует такой вещи, как автоматическое распространение проверенного контракта на исключение

и поправьте меня, если я ошибаюсь, это именно то, что делает Java, о чем вы упоминали в качестве примера ранее.

@aleksey-bykov
Этот:

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

function dontCare() {
   return mightThrow();
}

Однако означает, что и mightThrow , и dontCare подразумеваются как throws string :

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

Не будет предложения throw , поскольку ошибка была обработана.
Этот:

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

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

Будет throws MyErrorType .

Что касается вашего примера keepAllValues , я не уверен, что вы имеете в виду, в вашем примере:

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

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

MyClass.keepAllValues будет рассматриваться как throws string , потому что mightThrow может выдать string , и эта ошибка не была обработана.

Что касается вашего примера keepAllValues , я не уверен, что вы имеете в виду

Я имел в виду, что исключения, поступающие необработанными из mightThrow , прерывают keepAllValues и заставляют его закончить в середине того, что он делал, оставляя его состояние поврежденным. Это проблема. Вы предлагаете закрыть глаза на эту проблему и притвориться, что она несерьезна. Я предлагаю решить эту проблему, потребовав, чтобы все проверенные исключения немедленно обрабатывались и явно перебрасывались. Таким образом, нет возможности непреднамеренно испортить государство. И хотя он все еще может быть поврежден, если вы выберете это, это потребует некоторого преднамеренного кодирования.

Подумайте об этом, есть 2 способа сделать исключения:

  • не обрабатывать их, что приводит к сбою, если сбой - это то, что вы хотите, тогда у нас все в порядке
  • если вы не хотите сбоя, вам нужно руководство относительно того, какое исключение вам нужно искать, и вот тут-то и появляется ваше предложение: проверенные исключения - все явно перечислены, чтобы вы могли обрабатывать их все и не ничего не пропустите

теперь, если мы решили использовать проверенные исключения, которые должным образом обрабатываются и предотвращают сбой, нам нужно исключить ситуацию, когда мы обрабатываем исключение, происходящее из нескольких слоев в глубину того места, где вы его перехватываете:

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
}

приведенный выше пример представляет собой интересный случай для рассмотрения, какой будет предполагаемая подпись:

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

еще один интересный случай

// 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
Итак, вы предлагаете, чтобы все ошибки обрабатывались так же, как с java?
Я не фанат этого (хотя я пришел из Java и до сих пор люблю его), потому что js/ts намного более динамичны, и их пользователи привыкли к этому.
Это может быть флаг, который заставляет вас работать с ошибками, если вы включаете его при компиляции (например, strictNullChecks ).

Мое предложение здесь не для решения необработанных исключений, код, который вы разместили, теперь сломается без реализации этой функции, и он также сломается в js.
Мое предложение просто позволит вам, как разработчику, быть более осведомленным о различных ошибках, которые могут быть вызваны, вам все равно решать, обрабатывать ли их или игнорировать.

Что касается вопроса деления на 0, то он не приводит к ошибке:

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

более осведомлены о различных ошибках, которые могут быть выброшены

нет смысла делать это, если они не могут с ними справиться, текущее предложение нежизнеспособно из-за случаев, которые я перечислил

Итак, вы предлагаете, чтобы все ошибки обрабатывались так же, как с java?

да, вот что значит проверенные исключения

@aleksey-bykov
Я не понимаю, почему любой из перечисленных вами случаев делает это предложение нежизнеспособным.

Нет проблем с обработкой ошибки, которая была выброшена вниз по цепочке вызовов, даже если я использую функцию, которая была выведена из выдачи DivisionByZero (независимо от того, где она была выброшена), я могу ее обработать. .
Я могу попытаться повторить это с другими аргументами, я могу показать пользователю сообщение о том, что что-то пошло не так, я могу зарегистрировать эту проблему, чтобы позже изменить свой код для ее обработки (если это происходит часто).

Опять же, это предложение ничего не меняет во время выполнения, поэтому все, что работало, продолжит работать, как и прежде.
Единственная разница в том, что у меня будет больше информации об ошибках, которые могут возникнуть.

я понимаю, что вы говорите, ничего не изменится во время выполнения javascript, однако ваше сообщение здесь состоит в том, чтобы дать пользователям некоторую иллюзию того, что они знают, что делают, обрабатывая исключение, которое пришло из 20 слоев ниже с той же уверенностью, что и они будут обрабатывать немедленное исключение

они просто не могут решить проблему, которая возникла на 20 слоев ниже

вы, конечно, можете его зарегистрировать, как и любое непроверенное исключение, но вы не можете это исправить

так что вранье вообще говоря, вранья в ТС хватает, не будем еще больше людей путать

@aleksey-bykov
То, что вы описываете, существует на всех языках, поддерживающих исключения.
Никто не говорил, что перехват исключения решит проблему, но позволит изящно с ней справиться.

Знание того, какие ошибки могут возникнуть при вызове функции, поможет разработчикам отделить ошибки, которые они могут обработать, и те, с которыми они не могут справиться.

Прямо сейчас разработчики могут не знать, что использование JSON.parse может привести к ошибке, но если это было частью lib.d.ts и IDE дала бы ему знать (например), тогда, возможно, он предпочтет справиться с этим делом.

вы не можете изящно справиться с проблемой, возникшей на 20 уровней ниже, потому что внутреннее состояние повреждено на 19 уровнях, и вы не можете туда попасть, потому что состояние является частным

чтобы быть конструктивным: я предлагаю потребовать, чтобы пользователи немедленно обрабатывали проверенные исключения и повторно вызывали их явно, таким образом мы исключаем непреднамеренную путаницу и отделяем проверенные исключения от непроверенных:

  • проверенное исключение: произошло в непосредственной близости и должно быть обработано, таким образом гарантируется, что состояние не повреждено и безопасно продолжить
  • непроверенное исключение: произошло в непосредственной близости или намного дальше, его нельзя обработать, потому что состояние было повреждено, вы можете зарегистрировать его или продолжить на свой страх и риск

SyntaxError в JSON.parse должен быть объявлен как проверенное исключение

@aleksey-bykov

Я не понимаю, почему нужно заставлять разработчиков делать то, чего они не хотят, то, чего они до сих пор не делали.

Вот пример:
У меня есть веб-клиент, в котором пользователь может писать/вставлять данные json, а затем нажимать кнопку.
Приложение принимает этот ввод и передает его сторонней библиотеке, которая каким-то образом анализирует этот json и возвращает json вместе с различными типами значений (строка, число, логическое значение, массив и т. д.).
Если эта сторонняя библиотека выдает SyntaxError , я могу восстановить: сообщите пользователю, что его ввод недействителен, и он должен повторить попытку.

Зная, какие ошибки могут возникнуть при вызове функции, разработчик может решить, что он может/хочет обрабатывать, а что нет.
Не имеет значения, насколько глубоко в цепочке возникла ошибка.

послушайте, вы, кажется, не понимаете, что я говорю, мы идем по кругу

позволяя SyntaxError бросить из сторонней библиотеки, вы предоставляете своему пользователю детали реализации вашего собственного кода, который должен быть инкапсулирован

в основном вы говорите, эй, это не мой код не работает, это та дурацкая библиотека, которую я нашел в Интернете и использовал, поэтому, если у вас есть проблемы с ней, имейте дело со сторонней библиотекой, а не со мной, я просто сказал то, что меня просили

и нет никакой гарантии, что вы все еще можете использовать экземпляр этой третьей библиотеки после этого SyntaxError, вы несете ответственность за предоставление гарантий пользователю, скажем, путем повторного создания стороннего элемента управления после того, как он бросил

Суть в том, что вам нужно отвечать за обработку внутренних исключений ( не всех, а только проверенных, умоляю вас )

Я понимаю, что вы говорите, но я не согласен с этим.
Вы правы, это в основном то, что я говорю.
Если я использовал стороннюю библиотеку, которая выдает ошибку, я могу решить, что делать с ней, или проигнорировать ее и позволить пользователю моего кода справиться с ней.
Для этого есть много причин, например, библиотека, которую я пишу, не зависит от пользовательского интерфейса, поэтому я не могу сообщить пользователю, что что-то не так, но тот, кто когда-либо использует мою библиотеку, может обрабатывать ошибки, возникающие при использовании my lib и обрабатывать их, взаимодействуя с пользователем.

Если библиотека остается в поврежденном состоянии при броске, то, вероятно, ее необходимо задокументировать.
Если я потом воспользуюсь такой библиотекой и в результате ошибки в ней мое состояние испортится то мне нужно это задокументировать.

Нижняя линия:
Это предложение предлагает больше информации о возникших ошибках.
Это не должно принуждать разработчиков делать что-то по-другому, просто облегчать им работу с ошибками, если они захотят.

вы можете не соглашаться, это нормально, давайте просто не будем называть их проверенными исключениями, пожалуйста, потому что то, как вы это формулируете, не является проверенными исключениями

давайте назовем их перечисленными или обнаруженными исключениями, потому что все, что вам нужно, это сообщить разработчикам о них

@aleksey-bykov
Справедливо, имя изменено.

@aleksey-bykov

вы не можете изящно справиться с проблемой, возникшей на 20 уровней ниже, потому что внутреннее состояние повреждено на 19 уровнях, и вы не можете туда попасть, потому что состояние является частным

Нет, вы не можете исправить внутреннее состояние, но, безусловно, можете исправить локальное состояние, и именно в этом смысл обработки его здесь, а не глубже в стеке.

Если ваш аргумент состоит в том, что нет способа быть уверенным в том, в каком состоянии находятся некоторые общие изменяемые значения при обработке исключения, то это аргумент против императивного программирования, и он не ограничивается этим предложением.

если каждый уровень обязан брать на себя ответственность за реакцию на исключение, поступающее непосредственно с уровня ниже, шансов на успешное восстановление гораздо больше, это идея проверенных исключений, как я это вижу.

другими словами, исключения, происходящие более чем на 1 уровень ниже, - это предложение, слишком поздно делать что-либо, кроме повторного создания всей инфраструктуры с нуля (если вам повезет, нет глобальных остатков, которые вы можете' т достичь)

предложение, как указано, в основном бесполезно, потому что нет надежного способа отреагировать на знание о том, что что-то плохое произошло за пределами вашей досягаемости.

Отлично. FWIW: я думаю, что если он будет добавлен, он должен по умолчанию обрабатывать методы броска или также помечать ваш метод как бросок. В противном случае это просто документация.

@agonzalezjr
Я думаю, что, как и большинство функций машинописного текста, вы также должны иметь возможность подписаться на эту функцию.
Точно так же, как не обязательно добавлять типы, не обязательно бросать/ловить.

Вероятно, должен быть флаг, чтобы сделать его обязательным, например --onlyCheckedExceptions .

В любом случае, эта функция также будет использоваться для вывода/проверки типов выбрасываемых исключений, а не только для документации.

@нитзантомер

Вот пример:
У меня есть веб-клиент, в котором пользователь может писать/вставлять данные json, а затем нажимать кнопку.
Приложение принимает этот ввод и передает его сторонней библиотеке, которая каким-то образом анализирует этот json и возвращает json вместе с различными типами значений (строка, число, логическое значение, массив и т. д.).
Если эта сторонняя библиотека выдает SyntaxError, которую я могу исправить: сообщите пользователю, что его ввод недействителен, и он должен повторить попытку.

Это, безусловно, одна из областей, где вся идея проверенных исключений становится туманной. Здесь также становится неясным определение _исключительной ситуации_.
Программа в вашем примере будет аргументом для того, чтобы JSON.parse было объявлено как вызывающее проверенное исключение.
Но что, если программа является HTTP-клиентом и вызывает JSON.parse на основе значения заголовка, присоединенного к HTTP-ответу, который содержит неправильно сформированное тело? Нет ничего значимого, что программа может сделать для восстановления, все, что она может сделать, это перебросить.
Я бы сказал, что это аргумент против объявления JSON.parse проверенными.

Все зависит от варианта использования.

Я понимаю, что вы предлагаете, чтобы это было под флагом, но давайте представим, что я хочу использовать эту функцию, поэтому я включил этот флаг. В зависимости от того, какую программу я пишу, она может либо помочь, либо помешать мне.

Даже классический java.io.FileNotFoundException является примером этого. Проверено, но может ли программа восстановиться? Это действительно зависит от того, что означает отсутствующий файл для вызывающего, а не для вызываемого.

@aluanhaddad

Это предложение не предлагает добавить какие-либо новые функции, а только добавить способ выразить в машинописном тексте то, что уже существует в javascript.
Ошибки выбрасываются, но в настоящее время машинописный текст не имеет возможности объявить их (при выдаче или отлове).

Что касается вашего примера, перехватив ошибку, программа может "изящно" выйти из строя (например, показать пользователю сообщение "что-то пошло не так"), перехватив эту ошибку, или она может игнорировать ее, в зависимости от программы/разработчика.
Если эта ошибка может повлиять на состояние программы, то при ее обработке можно сохранить допустимое состояние, а не неисправное.

В любом случае разработчик должен решить, сможет ли он восстановиться после возникшей ошибки или нет.
Он также должен решить, что значит восстановить, например, если я пишу этот http-клиент для использования в качестве сторонней библиотеки, я мог бы захотеть, чтобы все ошибки, выдаваемые из моей библиотеки, были одного типа:

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

Теперь в моей библиотеке, когда я анализирую ответ, используя JSON.parse , я хочу поймать выброшенную ошибку, а затем выдать свою собственную ошибку:

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

Если эта функция будет реализована, то мне будет легко объявить это поведение, и пользователям моей библиотеки будет понятно, как оно работает и не работает.

Это предложение не предлагает добавить какие-либо новые функции, а только добавить способ выразить в машинописном тексте то, что уже существует в javascript.

Я знаю. Я говорю об ошибках, которые TypeScript будет выдавать в соответствии с этим предложением.
Мое предположение состояло в том, что это предложение подразумевало различие между проверенными и непроверенными спецификаторами исключений (выведенными или явными), опять же только для целей проверки типов.

@aluanhaddad

То, что вы сказали в предыдущем комментарии:

Но что, если программа является HTTP-клиентом и вызывает JSON.parse на основе значения заголовка, прикрепленного к HTTP-ответу, который содержит неправильно сформированное тело? Нет ничего значимого, что программа может сделать для восстановления, все, что она может сделать, это перебросить.

То же самое относится к возврату null , когда моя функция объявлена ​​возвращающей результат.
Если разработчик решит использовать strictNullChecks , вы можете сказать то же самое, если функция возвращает null (вместо броска), тогда в том же сценарии «нет ничего значимого, что программа может сделать выздороветь».

Но даже без использования флага onlyCheckedExceptions эта функция по-прежнему полезна, потому что компилятор будет жаловаться, например, если я попытаюсь поймать ошибку как string , когда объявлена ​​функция, выбрасывающая только Error .

Хорошая идея, это было бы полезно, но не строго/безопасно для типов, поскольку нет способа узнать, какие вложенные вызовы могут вам бросить.

Это означает, что если у меня есть функция, которая может генерировать исключение типа A, но внутри я вызываю вложенную функцию и не помещаю ее в try catch, она будет генерировать исключение типа B моему вызывающему объекту.
Таким образом, если вызывающий объект ожидает только исключения типа A, нет никакой гарантии, что он не получит другие типы из вложенных исключений.

(тема слишком длинная, поэтому извините, если я пропустил этот комментарий)

@shaipetel
В предложении говорится, что компилятор выведет типы необработанных ошибок и добавит их в сигнатуру функции/метода.
Таким образом, в случае, когда вы описали, ваша функция выдаст A | B , если B не было обработано.

О, я вижу. Он будет детализировать все методы, которые я вызываю, и соберет все возможные типы исключений?
Я бы хотел, чтобы это произошло, если это возможно. Видите ли, у разработчика всегда может быть неожиданное исключение, которое не будет объявлено, и в этом случае всегда возможны «объект не установлен в экземпляр» или «деление на 0» или подобные исключения всегда возможны практически из любой функции.
ИМХО, лучше всего было бы обрабатываться как в C#, где все исключения наследуются от базового класса, имеющего сообщение, и вообще не разрешать выбрасывать развернутый текст или другие объекты. Если у вас есть базовый класс и наследование, вы можете каскадировать свои уловы и обрабатывать ожидаемую ошибку в одном блоке, а другую неожиданную - в другом.

@shaipetel
В javascript все ошибки основаны на классе Error , но вы не ограничены выбрасыванием ошибок, вы можете выбрасывать что угодно:

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

Да, я знаю, как работает JavaScript, мы обсуждаем TypeScript, который накладывает дополнительные ограничения.
Я предложил, ИМХО, хорошим решением было бы заставить TypeScript следовать обработке исключений, которая требует, чтобы все выбрасываемые исключения относились к определенному базовому классу, а не позволяли напрямую выбрасывать развернутые значения.

Таким образом, он не позволит «выбросить 0» или «выдать« какую-то ошибку »».
Точно так же, как JavaScript позволяет многое, чего нет в TypeScript.

Спасибо,

@нитзантомер

Применяется то же самое для возврата нуля, когда моя функция объявляется возвращающей результат.
Если разработчик решит использовать strictNullChecks, вы можете сказать то же самое, если функция возвращает значение null (вместо выбрасывания), тогда в том же сценарии «нет ничего значимого, что программа может сделать для восстановления».

Но даже без использования флага onlyCheckedExceptions эта функция по-прежнему полезна, потому что компилятор будет жаловаться, например, если я попытаюсь поймать ошибку в виде строки, когда функция объявлена ​​​​выбрасывающей только Error.

Я понимаю, что вы говорите, это имеет смысл.

@shaipetel , как обсуждалось ранее в этом предложении и в других местах, создание подклассов, встроенных в такие функции, как Error и Array , не работает. Это приводит к различному поведению в широко используемых средах выполнения и целях компиляции.

Перехват значений различных типов, не являющихся ошибками, является наиболее жизнеспособным способом представления предлагаемой функциональности. На самом деле я не вижу в этом проблемы, поскольку механизмы распространения исключений, использующие эту функцию, скорее всего, приведут к ошибкам, которые являются гораздо более конкретными и гораздо более полезными, чем, скажем, трассировка стека или другие свойства, специфичные для ошибок.
Расширение Error нецелесообразно. Это не будет жизнеспособно, пока все цели ниже es2015 больше не будут использоваться.

Если это предложение косвенно ведет к большему количеству подклассов функции Error , то я думаю, что это плохая идея. Такое возражение полностью отделено от любых философских возражений по поводу использования исключений для потока управления или определения исключительных обстоятельств. Поэтому, если бы это предложение было принято, я бы ожидал чрезвычайно громкой и прямолинейной документации относительно правильного использования и необходимости избегать создания подклассов Error .

Мне бы хотелось, чтобы была какая-то вспомогательная логика для обработки типизированных исключений, я рефакторинг много промис-кода для использования async/await, который в настоящее время выглядит так:

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

Тогда в новом мире это выглядит так:

{
    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); } 
}

Это нормально, но требует больше кода и в некоторых отношениях немного менее читабельно, поэтому было бы здорово, если бы была какая-то поддержка для:

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

Что выведет предыдущий бит, но как синтаксический сахар, вы могли бы даже сделать это как дженерики, но это немного противнее:

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

@грофит
хотя я бы хотел, чтобы машинописный текст поддерживал то, что вы предлагаете, на мой взгляд, это не связано с этой проблемой.
То, что вы предлагаете, может быть реализовано даже без реализации того, о чем эта проблема, просто компилятор не сможет жаловаться (например), что NotFoundError не выбрасывается.

Я думал, что это было предложение для типизированных уловов, а не какая-либо проблема, я просто не хотел дублировать темы, тогда опубликую это в отдельной проблеме.

@грофит
Типизированные перехваты также являются частью запроса функции, а также возможностью сообщить компилятору, какие типы ошибок могут быть выброшены из функций (и тогда компилятор также сможет сделать вывод, какие типы ошибок могут быть выброшены).

На мой взгляд, типизированные уловы могут быть реализованы без других частей. Откройте новую проблему, может быть, команда TS решит пометить ее как дубликат, я не знаю.

@грофит

Что выведет предыдущий бит, но как синтаксический сахар, вы могли бы даже сделать это как дженерики, но это немного противнее:

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

не будет работать, потому что подразумевает генерацию кода, основанного только на типах, тогда как

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

передает функцию Error в каждом случае и, вероятно, реализуется с помощью instanceof . Такой код токсичен, поскольку поощряет расширение Error , что очень плохо.

@nitsantomer Я согласен, что это отдельная тема. В OP есть примеры типов перехвата, которые не расширяют Error .

@aluanhaddad
Для того, что запрашивает @grofit , вы можете сделать что-то вроде:

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

Где isXXX(error) являются охранниками типа в форме:
function isXXX(error): error is XXX { ... }

@nitzantomer конечно, но проблема не в охранниках типов. проблема в том

class MyError extends Error {
  myErrorInfo: string;
}

что проблематично, но уже обсуждалось здесь. Я не хочу зацикливаться на этом, но многие крупные и хорошо известные кодовые базы испытали негативное влияние, приняв эту неправильную практику. Расширение Error — плохая идея.

@aluanhaddad
Я знаю об этом, поэтому я предложил способ выполнить то, что запросил @grofit, но без необходимости расширения Error . Разработчик по-прежнему сможет расширить Error , если захочет, но компилятор сможет генерировать js-код, в котором не нужно использовать instanceof

@nitzantomer Я понимаю, что вы в курсе, но я не понимал, что вы предлагаете @grofit , что звучит как хорошая идея.

Я просто избиваю дохлую лошадь, потому что мне не нравится иметь дело с API, которые хотят заставить меня использовать такие шаблоны. Тем не менее, я извиняюсь, если я увел это обсуждение не по теме.

Были ли какие-либо мысли в этом обсуждении? Я хотел бы видеть предложение throws в машинописном тексте.

@aluanhaddad Вы продолжаете говорить, что расширение Error — это плохая практика. Не могли бы вы рассказать об этом немного подробнее?

Это долго обсуждалось. По сути, вы не можете надежно расширять встроенные типы. Поведение сильно различается в зависимости от комбинации сред и настроек --target .

Единственная причина, по которой я спросил, заключается в том, что ваши комментарии были первым, что я услышал об этом. Немного погуглив, я нашел только статьи, объясняющие, как и почему _should_ extend Error .

Нет, это не сработает. Вы можете подробно прочитать об этом в https://github.com/Microsoft/TypeScript/issues/12123 и многочисленных связанных проблемах.

Это будет работать, но только в «специфических средах», которых становится большинство по мере адаптации ES6.

@nitzantomer : Как вы думаете, возможно ли реализовать проверку TSLint, которая информирует разработчика, когда функция с @throws вызывается без окружения try/catch ?

@bennyn Я пользователь TSLint, но я никогда не занимался созданием новых правил.
Я действительно не знаю, возможно ли это (хотя я предполагаю, что да), и если да, то насколько просто.

Если вы попробуете, пожалуйста, обновите здесь, спасибо.

@shaipetel (а также @nitsantomer)

Ре:

Да, я знаю, как работает JavaScript, мы обсуждаем TypeScript, который накладывает дополнительные ограничения.
Я предложил, ИМХО, хорошим решением было бы заставить TypeScript следовать обработке исключений, которая требует, чтобы все выбрасываемые исключения относились к определенному базовому классу, а не позволяли напрямую выбрасывать развернутые значения.
Таким образом, он не позволит «выбросить 0» или «выдать« какую-то ошибку »».
Точно так же, как JavaScript позволяет многое, чего нет в TypeScript.

Не уверен, что это то, что вы предлагали @shaipetel, но на всякий случай ... Я бы предостерег от того, чтобы Typescript ограничивал throw только возвратом Error s. Будущие функции асинхронного рендеринга React работают под капотом throw ing Promise s! (Звучит странно, я знаю... но я читал, что они оценили это по сравнению с генераторами async / await и yield / yield* для своего варианта использования. и я уверен, что знаю, что они делают!)

throw ing Error s — это, я уверен, 99% вариантов использования, но не 100%. Я не думаю, что Typescript должен ограничивать пользователей только throw вещами, которые расширяют базовый класс Error . Это, безусловно, гораздо более продвинутый метод, но у throw есть и другие варианты использования, кроме ошибок/исключений.

@микелеонард
Я согласен, есть много примеров существующего кода js, который выдает множество типов (я видел много throw "error message string" ).
Новая функция React — еще один хороший пример.

Мои два цента: я конвертирую код в асинхронный/ожидающий, поэтому меня бросают (кроме того, я ненавижу исключения). На мой взгляд, было бы неплохо иметь пункт о бросках, который обсуждается в этом выпуске. Я думаю, что это тоже было бы полезно, даже если бы было разрешено «выбрасывать любые». (Кроме того, возможно, опция «nothrows» и компилятора, которая по умолчанию использует значение «nothrows».)

Это похоже на естественное расширение, позволяющее печатать то, что выдает функция. Возвращаемые значения могут быть необязательно введены в TS, и мне кажется, что throw — это просто еще один тип возврата (о чем свидетельствуют все альтернативы, предложенные для избегания throw, например https://stackoverflow.com/a/39209039/162530).

(Лично я бы также хотел иметь (необязательную) опцию компилятора, чтобы любой вызывающий объект функции, объявленной как throws, должен либо также объявлять как throws, либо перехватывать их.)

Мой текущий вариант использования: я не хочу преобразовывать всю свою кодовую базу [Angular] для использования исключений для обработки ошибок (поскольку я их ненавижу). Я использую async/await в деталях реализации своих API, но конвертирую throw в обычные Promises/Observables, когда API возвращается. Было бы неплохо, если бы компилятор проверял, что я улавливаю правильные вещи (или, в идеале, улавливаю их вообще).

@aleksey-bykov Давайте не будем менять природу JavaScript, давайте просто добавим в него типизацию. :)

добавление ввода уже изменяет его (вырезает код, который не имеет смысла),
таким же образом мы можем ужесточить обработку исключений

В четверг, 26 июля 2018 г., 20:22 Джо Пи, [email protected] написал:

@aleksey-bykov https://github.com/aleksey-bykov Не будем менять
характер JavaScript, давайте просто добавим к нему типизацию. :)


Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/Microsoft/TypeScript/issues/13219#issuecomment-408274156 ,
или заглушить тему
https://github.com/notifications/unsubscribe-auth/AA5PzfUNS5E093Z74WA4WCUaTyWRRZC3ks5uKl1FgaJpZM4LXwLC
.

Для обещаний, если вы используете цепочку then вместо async/await и избегаете использования throw , на самом деле все работает довольно красиво. Вот пример эскиза концепции:

https://bit.ly/2NQZD8i - ссылка на игровую площадку

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)
})

Предикат catch удаляет типы из объединения, возвращая к ним reject() . Единственное, что не совсем работает, это вывод типов union-parameter (вероятно, ошибка)

Проблемы, рассматриваемые в приведенном выше примере:

  • это не ограничивается ошибками, вы можете отказаться от чего угодно (хотя, вероятно, это плохая идея)
  • это не ограничивается использованием instanceof, вы можете использовать любую защиту типа предиката, чтобы удалить ошибки из объединения

Основная проблема, которую я вижу при использовании этого для всего кода, - это сигнатуры обратного вызова, чтобы заставить его работать совместимым образом, «тип возврата» по умолчанию будет «может выбросить любой», и если вы хотите ограничить это, вы должны сказать throws X или throws never , если это не так, например

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

Версия без подписи:

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

на самом деле по умолчанию

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

чтобы гарантировать, что весь текущий код компилируется.

В отличие от strictNullChecks , где нулевые возвращаемые значения довольно редки, я думаю, что исключения в JS довольно распространены. Моделирование их в файлах .d.ts может быть не так уж и плохо (импортируйте типы из зависимостей для описания ваших ошибок), но это определенно будет нетривиальным усилием и приведет к огромным объединениям.

Я думаю, что хорошей золотой серединой было бы сосредоточиться на промисах и async/await , поскольку промис уже является оболочкой, а асинхронный код — это то место, где обработка ошибок чаще всего разветвляется в типичных сценариях. Другие ошибки будут «не проверены»

@spion-h4 можно ли это (уже/будет) использовать в машинописном тексте?

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

@bluelovers
нет, машинописный текст в настоящее время не поддерживает ключевое слово throws , это то, о чем эта проблема.

Следует отметить (не то чтобы это обязательно блокировало это предложение), так это то, что без номинальных классов ввод встроенных ошибок по-прежнему не особенно полезен, поскольку все они структурно одинаковы.

например:

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
Этот вопрос не имеет ничего общего с предложением.
Вы можете столкнуться с той же проблемой, выполнив следующее:

class BaseClass {
    propA!: string;
}

class MyClass1 extends BaseClass { }

class MyClass2 extends BaseClass { }

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

Это языковое ограничение, которое влияет на множество вариантов использования.

@ahejlsberg отлично читал о типизированных исключениях: https://www.artima.com/intv/handcuffs.html

Я считаю, что TypeScript может избежать этих проблем. TypeScript — это все о _прагматичных_ решениях _реальных_ проблем. По моему опыту, в больших кодовых базах JavaScript и TypeScript обработка ошибок является одной из самых больших проблем — невероятно сложно увидеть, какие ошибки функции _могут_ выдавать, а я _могу_ захотеть обработать. Это возможно только при чтении и написании хорошей документации (мы все знаем, насколько хороши мы в этом / s) или просмотре реализации, и поскольку, в отличие от возвращаемых значений, исключения просто автоматически распространяются через вызовы функций, недостаточно просто проверьте непосредственно вызываемую функцию, но чтобы поймать их все, вам также придется проверять вызовы вложенных функций. _Это реальная проблема_.

Ошибки в JavaScript на самом деле очень полезные значения. Многие API в NodeJS выдают подробные объекты ошибок с четко определенными кодами ошибок и предоставляют полезные метаданные. Например, ошибки из child_process.execFile() имеют такие свойства, как exitCode и stderr , ошибки из fs.readFile() имеют такие коды ошибок, как ENOENT (файл не найден ) или EPERM (недостаточно прав). Я знаю много библиотек, которые также делают это, например, драйвер базы данных pg дает вам достаточно метаданных об ошибке, чтобы узнать, какое именно ограничение столбца вызвало сбой INSERT .

Вы можете увидеть тревожное количество ненадежных проверок регулярных выражений в сообщениях об ошибках в кодовых базах, потому что люди не знают, что ошибки имеют правильные коды ошибок и что они из себя представляют.

Если бы мы могли определить в объявлениях @types и lib.d.ts , какие ошибки могут вызывать эти функции, TypeScript мог бы помочь нам со структурой ошибки — какие возможные ошибки могут быть, что значения кодов ошибок, какими свойствами они обладают. Это _не_ о типизированных исключениях и поэтому полностью позволяет избежать всех проблем с типизированными исключениями. Это _прагматичное решение_ _реальной проблемы_ (в противоположность совету людям использовать вместо этого монадические возвращаемые значения ошибок - реальность земли JavaScript выглядит иначе, функции выдают ошибки).

Аннотация может быть совершенно необязательной (или может быть сделана обязательной с помощью флага компилятора). Если это не указано в файле объявления, функция просто выдает any (или unknown ).
Функция может вручную объявить, что никогда не будет бросать с помощью throws never .
Если реализация доступна, функция генерирует объединение всех типов исключений функций, которые она вызывает, и свои собственные операторы throw , которые не находятся внутри блока try с catch пункт
Если один из них выдает any , функция тоже выдает any — это нормально, на каждой границе функции у разработчика есть возможность исправить это через явную аннотацию.
И во многих случаях, когда одна известная функция вызывается и заворачивается в try/catch (например, чтение файла и его обработка, если он не найден), TypeScript может вывести тип в предложении catch.

Для этого нам не нужны ни подклассы Error, ни instanceof — типы ошибок могут быть интерфейсами, которые определяют коды ошибок с помощью строковых литералов, а TypeScript может различать объединение в коде ошибки или использовать защиту типов.

Определение типов ошибок

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 })
    }
}

Помимо ошибок, также возможно отметить другие виды побочных эффектов, таких как

  • Расхождение (бесконечный цикл / невозврат)
  • ИО
  • Недетерминизм ( Math.random )

Это напоминает мне Koka из MSR, который может помечать эффекты возвращаемых типов.

Предложение:

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

Мне нравится идея объявления типа ошибки! Это будет большим подспорьем для людей, которые в нем нуждаются, и не сделает ничего плохого для людей, которым это не нравится. Теперь у меня есть проблема в моем проекте узла, которую можно было бы решить быстрее с помощью этой функции. Я должен ловить ошибки и отправлять правильный http-код обратно - для этого я должен всегда проверять все вызовы функций, чтобы найти исключения, которые я должен обрабатывать - и это не весело -_-

PS. Синтаксис в https://github.com/Microsoft/TypeScript/issues/13219#issuecomment -428696412 выглядит очень хорошо - &! и обертывание типов ошибок в <> просто потрясающе.

Я тоже за это. Теперь все ошибки не типизированы, и это довольно раздражает. Хотя я надеюсь, что большая часть throws может быть получена из кода, который нам приходится писать везде.

Таким образом, люди могут даже написать правила tslint, которые заставят разработчика отлавливать недружественные пользователю ошибки на остальных конечных точках и т. д.

Я был действительно удивлен, что эта функциональность не была _уже_ внутри TypeScript. Одно из первых вещей, которые я пошел объявить. Было бы нормально, если бы компилятор применил или не применил это, если вы можете получить информацию о том, что будет выдана ошибка, и какой тип ошибок будет выдан.

Даже если мы не получим &! , а просто получим Throws<T> , это будет :+1:

Это именно то, что я имею в виду, было бы намного красивее, если бы TypeScript поддерживал эти пункты.

Другой возможной функцией было бы каким-то образом принудительно (возможно, на уровне функции или на уровне модуля) strictExceptionTracking - это означало бы, что вам придется вызывать что-либо, объявленное как throws X с выражением try! invokeSomething() как в Rust и Swift.

Он просто скомпилируется в invokeSomething() , но возможность исключений будет видна в коде, что упростит обнаружение, если вы оставляете что-то изменяемое в плохом переходном состоянии (или оставляете выделенные ресурсы неиспользованными).

@be5invis

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

Я просто даю свои пять копеек, но &! выглядит невероятно уродливым для меня.
Я думаю, что люди, плохо знакомые с машинописным текстом, будут отталкиваться от этого символа, он действительно непривлекательный.
Простой throws , на мой взгляд, более явный, интуитивно понятный и простой.

Кроме этого, +1 для типизированных исключений. 👍

Я хотел бы добавить свой +1 для типизированных и проверенных исключений.
Я широко использовал типизированные исключения в своем коде. Я полностью осведомлен о ловушках instanceof , я фактически использовал его в своих интересах, имея возможность писать общие обработчики для связанных ошибок, которые наследуются от общего базового класса. Большинство других методов, с которыми я сталкивался, чтобы избежать наследования от базового класса Error, оказались (по крайней мере) столь же сложными и проблематичными по-разному.
Проверенные исключения — это улучшение, которое, на мой взгляд, позволяет лучше понять кодовую базу при помощи статического анализа. В кодовой базе достаточной сложности может быть легко пропустить, какие исключения выбрасываются конкретной функцией.

Для тех, кто ищет защиту от ошибок во время компиляции в машинописном тексте, вы можете использовать мою библиотеку ts-results .

@vultix для меня, к сожалению, разделение недостаточно четкое, чтобы поместить это в командную настройку.

@vultix Подход к вашей библиотеке обсуждался выше, и это полная противоположность тому, что решает эта функция.
В Javascript уже есть механизм обработки ошибок, он называется исключениями, но в typescript отсутствует способ его описания.

@nitsantomer Я полностью согласен с тем, что эта функция необходима для машинописного текста. Моя библиотека — не более чем временная замена, пока эта функция не будет добавлена.

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

Я знаю, что немного опоздал в эту игру, но ниже, кажется, немного больше синтаксиса Typescripty, чем запятая.

Iterable<number> throws TypeError | RangeError

Но я все еще хорош с запятой. Я просто хочу, чтобы у нас было это в языке.
Главный из них: я бы хотел, чтобы это было на JSON.parse() , потому что многие мои коллеги, кажется, забывают, что JSON.parse может вызвать ошибку, и это сэкономит много времени на обмен данными с запросы на вытягивание.

@WORMSS
Я полностью согласен.
Мое предложение включало синтаксис, который вы рекомендуете:

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

Можем ли мы следовать шаблону объявления ошибок языка ООП, такого как Java? Ключевое слово «throws» используется для объявления того, что нам нужно «try..catch» при использовании функции с потенциальной ошибкой.

@allicanseenow
Предложение касается необязательного использования предложения throws.
То есть вам не нужно будет использовать try/catch, если вы используете функцию, которая выбрасывает.

@allicanseenow вы можете прочитать мою статью выше для контекста, включая связанную статью: https://github.com/microsoft/TypeScript/issues/13219#issuecomment -416001890

Я думаю, что это одна из НЕДОСТАЮЩИХ вещей в системе машинописи.
Это чисто опционально, не изменяет испускаемый код, помогает работать с библиотеками и нативными функциями.

Кроме того, знание того, какие ошибки могут быть вызваны, может позволить редакторам «автозаполнять» предложение catch с условиями if для обработки всех ошибок.

Я имею в виду, что на самом деле даже маленькое приложение заслуживает надлежащей обработки ошибок — самое худшее сейчас то, что пользователи не знают, когда что-то может пойти не так.

Для меня это ТОП недостающая функция сейчас.

@RyanCavanaugh , было много отзывов с тех пор, как был добавлен ярлык «Ожидает дополнительных отзывов». Может ли кто-нибудь из членов команды машинописного текста внести свой вклад?

@nitzantomer Я поддерживаю идею добавления бросков в TypeScript, но разве необязательный try/catch не допускает потенциально неточные объявления бросков при использовании во вложенных функциях?

Чтобы разработчик мог поверить в точность предложения throws метода, ему пришлось бы сделать опасное предположение, что ни в одной точке иерархии вызовов этого метода необязательное исключение не было бы проигнорировано и выброшено вверх по стеку без уведомления. Я думаю, что @ajxs , возможно, намекнул на это в конце своего комментария, но я думаю, что это будет большой проблемой. Особенно с учетом того, насколько фрагментированы большинство библиотек npm.

@КоннорСиннотт
Надеюсь, я вас правильно понял:

Компилятор выведет брошенные типы, например:

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

На самом деле будет function fn(): throws string { ... } .
Компилятор также выдаст ошибку при несоответствии заявленных ошибок реальным:

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

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

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

Компилятор должен жаловаться, что fn2 выдает string | MyError , а не string .

Имея все это в виду, я не вижу, насколько это предположение более опасно, чем предположение о том, что другие объявленные типы, которым разработчик доверяет при использовании других библиотек, фреймворков и т. д.
Я не уверен, что действительно рассмотрел все варианты, и я буду рад, если вы сможете придумать интересный сценарий.

И даже с этой проблемой, это почти то же самое, что и сейчас:

// 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) { ... }
    }
}

Мы можем защитить нас даже от этого, но для этого потребуется синтаксис, который немного отличается от javascript:

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

Это будет скомпилировано в:

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

Привет! Спасибо за ответ! На самом деле, вывод бросков, как вы упомянули, смягчает проблему, о которой я думал. А вот второй пример, который вы перечислили, интересен.

Данный

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;
}

Чтобы явно объявить MyVeryImportantError, мне пришлось бы также явно объявить все дополнительные ошибки стека вызовов, которых может быть несколько в зависимости от глубины приложения. Кроме того, потенциально утомительно. Я, конечно, не хотел бы погружаться во всю цепочку вызовов, чтобы сгенерировать список потенциальных ошибок, которые могут возникнуть в пути, но, возможно, IDE могла бы помочь.

Я думал предложить какой-то оператор распространения, чтобы позволить разработчику явно объявлять свою ошибку и просто выбрасывать остаток.

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

Но есть и более простой способ добиться того же результата: просто отбросить объявление throws.

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

В связи с этим возникает вопрос: когда я увижу преимущество использования ключевого слова throws вместо того, чтобы просто позволить машинописному тексту делать выводы об ошибках?

@КоннорСиннотт

Он ничем не отличается от других языков, использующих предложение throw, например java.
Но я думаю, что в большинстве случаев вам не придется иметь дело с большим количеством типов. В реальном мире вы обычно имеете дело только с string , Error (и подклассами) и другими объектами ( {} ).
И в большинстве случаев вы просто сможете использовать родительский класс, который захватывает более одного типа:

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();
}

Что касается неявного использования предложения throw по сравнению с тем, чтобы его выводил компилятор, я думаю, что это похоже на объявление типов в машинописном тексте, во многих случаях вам не нужно, но вы можете сделать это, чтобы лучше документировать свой код, чтобы тот, кто прочитает его позже, сможет лучше понять его.

Другой подход, если вам нужна безопасность типов, состоит в том, чтобы просто рассматривать ошибки как значения а-ля Golang:
https://gist.github.com/brandonkal/06c4a9c630369979c6038fa363ec6c83
Тем не менее, было бы неплохо иметь эту функцию.

@brandonkal
Этот подход уже обсуждался ранее в теме.
Это возможно, но при этом игнорируется неотъемлемая часть/функция, которую позволяет нам javascript.

Я также думаю, что спецификатор noexcept (#36075) является самым простым и лучшим решением на данный момент, поскольку для большинства программистов создание исключений считается анти-шаблоном.

Вот несколько интересных функций:

  1. Проверка функций не приведет к ошибке:
noexcept function foo(): void {
  throw new Error('Some exception'); // <- compile error!
}
  1. Предупреждение о избыточных блоках try..catch :
try { // <- warning!
  foo();
} catch (e) {}
  1. Resolve всегда обещает:
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`
}

@мошест

как и для большинства программистов, выбрасывание исключений считается антишаблоном.

Думаю, я не вхожу в группу "большинства программистов".
noexcept — это круто и все такое, но это не то, о чем этот выпуск.

Если есть инструменты, я их использую. throw , try/catch и reject есть, поэтому я буду использовать их.

Было бы неплохо, если бы они были правильно напечатаны в машинописном тексте.

Если есть инструменты, я их использую. throw , try/catch и reject есть, так что я буду использовать их.

Конечно. Я также буду использовать их. Я хочу сказать, что noexcept или throws never будут хорошим началом для этой проблемы.

Глупый пример: я хочу знать, что если я позвоню Math.sqrt(-2) , он никогда не выдаст никакой ошибки.

То же самое относится и к сторонним библиотекам. Это дает больше контроля над кодом и крайними случаями, с которыми мне как программисту нужно справляться.

@мошест
Но почему?
Это предложение включает в себя все преимущества предложения «без ожиданий», так зачем соглашаться на меньшее?

Потому что это займет много времени (этой проблеме уже 3 года), и я надеялся, что мы хотя бы сможем начать с самой базовой функции.

@мошест
Ну, я предпочитаю лучшую функцию, которая требует больше времени, чем частичное решение, которое займет меньше времени, но мы останемся с ним навсегда.

Я чувствую, что noexcept можно легко добавить позже, на самом деле это может быть отдельная тема.

noexcept совпадает с throws never .

TBH Я думаю, что более важно иметь какой-то механизм, гарантирующий обработку исключений (например, не используемый error в Go), а не предоставлять подсказки типа для try/catch

@roll не должно быть никаких «гарантий».
нет необходимости обрабатывать исключения в javascript, и это также не должно быть обязательным в машинописном тексте.

Это может быть вариант как часть строгого режима typecrypt, когда функция должна либо перехватывать ошибку, либо явно объявлять ее throws и передавать ее дальше.

Чтобы добавить мои 5 центов: я вижу исключения, похожие на функцию, возвращающую null : это то, чего вы обычно не ожидаете. Но если это происходит, возникает ошибка времени выполнения, что плохо. Теперь TS добавил «необнуляемые типы», чтобы напомнить вам об обработке null . Я вижу, что добавление throws делает еще один шаг вперед, напоминая вам об обработке исключений. Вот почему я думаю, что эта функция определенно нужна.

Если вы посмотрите на Go, который возвращает ошибки, а не выдает их, вы увидите еще яснее, что обе концепции не так уж отличаются. Кроме того, это поможет вам глубже понять некоторые API. Возможно, вы не знаете, что некоторые функции могут генерировать ошибки, и замечаете это гораздо позже в продакшене (например, JSON.parse , кто знает, сколько их еще?).

@ obedm503 На самом деле это философия TS: по умолчанию компилятор ни на что не жалуется. Вы можете включить параметры, чтобы рассматривать определенные вещи как ошибку, или включить строгий режим, чтобы включить все параметры одновременно. Итак, это должно быть данностью.

Мне нравится эта предлагаемая функция.
Типизация исключений и вывод могут быть одной из лучших вещей во всей истории программирования.
❤️

Привет, я только что потратил немного времени, пытаясь найти обходной путь с тем, что у нас есть в настоящее время в TS. Поскольку нет способа получить тип выброшенных ошибок в области действия функции (и, кстати, получить тип текущего метода), я выяснил, как мы могли бы явно установить ожидаемые ошибки в самом методе. Позже мы могли бы получить этот тип и, по крайней мере, узнать, что может быть выброшено в методе.

Вот мой ПОС

/***********************************************
 ** 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)

Большое спасибо за положительный отзыв! Я понимаю, что было бы проще реорганизовать существующий код без необходимости оборачивать весь возвращаемый тип в тип throwable . Вместо этого мы могли бы просто добавить это к возвращаемому типу, следующий код позволяет добавить выбрасываемые ошибки следующим образом:

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

Это единственное изменение для части примера, вот объявление новых типов:

/***********************************************
 ** 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>;
};

Я также добавил новый тип exceptionsOf , который позволяет извлекать ошибки функции для повышения ответственности. Например:

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;
}

Поскольку exceptionsOf получает объединение ошибок, вы можете эскалировать столько критических методов, сколько хотите:

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();
}

Мне не нравится использование typeof , если я найду лучший способ, я дам вам знать

Вы можете проверить здесь подсказки результатов : наведите typedError на строку 106.

@Xample Это отличное решение с теми инструментами, которые у нас есть сейчас!
Но я думаю, что на практике этого недостаточно, поскольку вы все еще можете делать такие вещи, как:

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

И тип b выводится как число, которое правильно, но только если оно заключено в попытку
Это не должно быть действительным

@luisgurmendezMLabs Я не совсем понимаю, что вы имеете в виду. Если вы будете следовать репозиторию @Xample в строке 56, вы увидите, что результат myNumber + 23 выводится как number , а myNumber: number & extraType<Error | CustomError> .

На другом этапе a в вашем примере никогда не заворачивается во что-то вроде Try Monad. У него вообще нет обертки, кроме пересечения с & extraType<Error | CustomError> .

Поздравляем с потрясающим дизайном @Xample 👏👏👏👏👏👏. Это действительно многообещающе и уже полезно даже без синтаксического сахара. Есть ли у вас какие-либо планы по созданию библиотеки типов для этого?

@ivawzh Я думаю, что на практике это может вызвать некоторые проблемы, поскольку:

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

Возврат этого типа функции выводится как число, и это не совсем правильно.

@luisgurmendezMLabs бросаемый тип находится в возвращаемом типе функции только как способ привязать тип ошибки к функции (и иметь возможность восстановить эти типы позже). Я никогда не возвращаю ошибки по-настоящему, я только выбрасываю их. Поэтому, если вы находитесь в блоке try или нет, ничего не изменится.

@ivawzh спасибо за вопрос, я только что сделал это для тебя

@luisgurmendezMLabs ах, хорошо, я понимаю вашу точку зрения, кажется, машинописный текст делает вывод только о первом найденном типе. Например, если у вас было:

function stillARiskyMethod() { 
    return superRiskyMethod();
}

возвращаемый тип StillARiskyMethod будет правильно определен, а

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

будет вводить возвращаемый тип только как superRiskyMethod() и отклонять тип anotherSuperRiskyMethod() . больше я не исследовал

По этой причине вам необходимо вручную эскалировать тип ошибки.

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

Я также просто хотел высказать свои мысли по этому поводу.

Я думаю, что это было бы действительно хорошей функцией для реализации, поскольку есть разные варианты использования для желания вернуть ошибку/нуль и желания что-то выбросить, и у этого может быть большой потенциал. Генерация исключений в любом случае является частью языка Javascript, так почему бы нам не дать возможность вводить их и делать выводы?

Например, если я выполняю много задач, которые могут привести к ошибке, мне было бы неудобно использовать оператор if для проверки возвращаемого типа каждый раз, когда это можно было бы упростить, используя try/catch, где, если есть выдает одну из этих задач, она будет обработана в catch без какого-либо дополнительного кода.

Это особенно полезно, когда вы собираетесь обрабатывать ошибки таким же образом ; например, в express/node.js я мог бы передать ошибку NextFunction (обработчик ошибок).

Вместо того, чтобы каждый раз делать if (result instanceof Error) { next(result); } , я мог бы просто обернуть весь код для этих задач в try/catch, и в своем улове я знаю, что, поскольку было выбрано исключение, я всегда буду хотеть передать это своему обработчик ошибок, поэтому может catch(error) { next(error); }

Также еще не видел, чтобы это обсуждалось (возможно, пропустило это, однако, в этой ветке довольно много комментариев!), Но если бы это было реализовано, было бы обязательным (например, ошибка компиляции) иметь функцию, которая выдает без использования Предложение throws в объявлении функции? Я чувствую, что это было бы неплохо сделать (мы не заставляем людей обрабатывать броски, это просто сообщит им, что функция действительно вызывает), но большая проблема здесь заключается в том, что если бы Typescript был обновлен таким образом, он, вероятно, сломать много существующего в настоящее время кода.

Изменить: еще один вариант использования, о котором я подумал, может заключаться в том, что это также поможет с созданием JSDocs с использованием тега @throws.

Я повторю здесь то, что я сказал в теперь совершенном выпуске .


Я думаю, что машинописный текст должен уметь определять тип ошибки большинства выражений JavaScript. Ускорение реализации создателями библиотек.

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();
}
  • Функция a мы знаем, что тип ошибки 'unlucky' , и если мы хотим быть очень осторожными, мы можем расширить его до Error | 'unlucky' , следовательно, includeError .
  • Функция b унаследует тип ошибки функции a .
  • Функция c практически идентична; 'unlucky' | 'fairly lucky' или Error | 'unlucky' | 'fairly lucky' .
  • Функция d должна будет выкинуть unknown , так как eval... неизвестен
  • Функция e перехватывает ошибку d , но так как в блоке catch есть throw , мы делаем вывод о ее типе 'too bad...' , здесь, поскольку только блок содержит throw 'primitive value' , мы могли бы сказать, что он не может выдать Error (поправьте меня, если я пропустил черную магию JS...)
  • Функция f наследуется от c так же, как b #$ наследуется от $ a .
  • Функция g наследует 'unlucky' от a и unknown от c , таким образом 'unlucky' | unknown => unknown

Вот некоторые параметры компилятора, которые, по моему мнению, должны быть включены, поскольку взаимодействие пользователей с этой функцией может варьироваться в зависимости от их навыков, а также от безопасности типов библиотек, от которых они зависят в данном проекте:

{
  "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
    }
  }
}

Что касается синтаксиса того, как выразить ошибку, которую может произвести любая функция, я не уверен, но я знаю, что нам нужна возможность сделать ее универсальной и понятной.

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;

Мой вариант использования, так как показ фактического варианта использования может показать, что у него есть значение:

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

Мне нужен приемник ошибок, чтобы предотвратить отправку заголовков ответа несколько раз для нескольких ошибок (обычно они не случаются, но когда они происходят, они делают это массово!)

Эта проблема по-прежнему отмечена тегом Awaiting More Feedback Что мы можем сделать, чтобы предоставить больше отзывов? Это единственная особенность, которой я завидую в языке Java.

У нас есть вариант использования, когда мы выдаем конкретную ошибку, когда вызов API возвращает код, отличный от 200:

interface HttpError extends Error {
  response: Response
}

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

Неспособность ввести блок catch приводит к тому, что разработчики забывают, что могут быть выброшены 2 возможных типа ошибок, и им нужно обрабатывать оба.

Я предпочитаю всегда выбрасывать объект Error:

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')) { .....}
  }
}

Почему эта функция по-прежнему «недостаточно отзывов»? Это так полезно при вызове API браузера, такого как indexedDB, localstorage. Это вызвало множество сбоев в сложном сценарии, но разработчик не может знать о программировании.

Гегель , по-видимому, обладает этой чертой в совершенстве.
https://hegel.js.org/docs#benefits (прокрутите до раздела «Типированная ошибка»)

Я бы хотел, чтобы TypeScript имел аналогичную функцию!

ДЛ;ДР;

  • Функция отклонения промисов должна быть напечатана
  • Любой тип, выброшенный в блоке try , должен выводиться с помощью объединения в аргумент ошибки блока catch
  • error.constructor должен быть правильно типизирован с использованием реального типа, а не только any , чтобы не пропустить возникшую ошибку.

Хорошо, возможно, мы должны просто уточнить, каковы наши потребности и ожидания:

Ошибки обычно обрабатываются тремя способами в js

1. Способ узла

Использование обратных вызовов (которые на самом деле могут быть набраны)

Пример использования:

import * as fs from 'fs';

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

Где fs.d.ts дает нам:

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

Поэтому ошибка пишется так

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

2. Путь обещания

Там, где обещание разрешается или отклоняется, хотя вы можете ввести разрешенное значение value , вы не можете ввести отклонение, которое часто называют reason .

Вот сигнатура конструктора промиса: Обратите внимание, что причина указана как any

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

Теоретически можно было бы напечатать их следующим образом:

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

Таким образом, теоретически мы все еще могли бы сделать преобразование 1-1 между обратным вызовом узла и обещанием, сохраняя весь ввод в этом процессе. Например:

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. Способ «попробуй и поймай»

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

Несмотря на то, что мы выдаем ошибку «Ошибка» за 2 строки до блока catch, TS не может ввести error (значение) как Error (тип).

Разве это не тот случай, когда использование промисов внутри функции async (пока нет волшебства). Используя наш обещанный обратный вызов узла:

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

Для машинописного текста нет недостающей информации, чтобы можно было предсказать, какой тип ошибки может быть выдан в области действия попытки.
Однако мы должны учитывать, что внутренние, собственные функции могут вызывать ошибки, которых нет в исходном коде, однако, если каждый раз, когда ключевое слово «throw» встречается в источнике, TS должен собирать тип и предлагать его в качестве возможного типа для ошибки. Эти типы, конечно, будут ограничены блоком try .

Это только первый шаг, и еще есть возможности для улучшения, например, строгий режим, заставляющий TS работать как в Java, то есть заставлять пользователя использовать рискованный метод (метод, который может что-то выдать) в пределах try Блок function example throw ErrorType { ... } , чтобы повысить ответственность за обработку ошибок.

И последнее, но не менее важное: не пропустите ошибку

Выбросить можно что угодно, не только Error или даже экземпляр объекта. Значение следующего действительно

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")
    }
}

Знать, что ошибка может иметь тип number | Error , было бы невероятно полезно. Однако, чтобы не забыть обработать возможный тип ошибки, на самом деле не лучшая идея использовать отдельные блоки if/else без строгого набора возможных результатов.
Однако случай переключения сделал бы это намного лучше, поскольку нас могут предупредить, если мы забудем сопоставить конкретный случай (какой из них будет откатом к предложению по умолчанию). Мы не можем (если мы не делаем что-то хакерское) переключать регистр типа экземпляра объекта, и даже если бы мы могли, мы могли бы выбросить что угодно (не только объект, но и логическое значение, строку и число, у которых нет «экземпляра»). Однако мы можем использовать конструктор экземпляра, чтобы узнать, к какому типу он относится. Теперь мы можем переписать приведенный выше код следующим образом:

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;
    }
}

Ужас… единственная оставшаяся проблема заключается в том, что TS не набирает error.constructor и, следовательно, нет способа сузить случай переключения (пока?), если бы он это сделал, у нас был бы безопасный язык типизированных ошибок для js.

Пожалуйста, прокомментируйте, если вам нужна дополнительная обратная связь

Была ли эта страница полезной?
0 / 5 - 0 рейтинги