Typescript: 제안: `throws` 절 및 형식화된 catch 절

에 만든 2016년 12월 29일  ·  135코멘트  ·  출처: microsoft/TypeScript

typescript 유형 시스템은 대부분의 경우에 유용하지만 예외를 처리할 때는 사용할 수 없습니다.
예를 들어:

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

여기서 문제는 두 가지입니다(코드를 살펴보지 않고).

  1. 이 기능을 사용할 때 오류가 발생할 수 있다는 것을 알 수 있는 방법이 없습니다.
  2. 오류의 유형이 무엇인지 명확하지 않습니다.

많은 시나리오에서 이는 실제로 문제가 되지 않지만 함수/메서드가 예외를 throw할 수 있는지 여부를 아는 것은 특히 다른 라이브러리를 사용할 때 다양한 시나리오에서 매우 유용할 수 있습니다.

(선택 사항) 확인된 예외를 도입하여 유형 시스템을 예외 처리에 사용할 수 있습니다.
확인된 예외가 동의되지 않는다는 것을 알고 있습니다(예: Anders Hejlsberg ). 하지만 선택 사항으로 만들면(그리고 나중에 추론할 수도 있습니다) 개발자, 도구 및 선적 서류 비치.
또한 대규모 프로젝트에서 의미 있는 사용자 지정 오류를 더 잘 사용할 수 있습니다.

모든 자바스크립트 런타임 오류는 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) { ... }

numberstring $ 이 아니기 때문에 컴파일에 실패합니다.

제어 흐름 및 오류 유형 추론

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 페이지 로 이동하여 다음을 읽으십시오.

구문 분석할 문자열이 유효한 JSON이 아닌 경우 SyntaxError 예외가 발생합니다.

그리고 이것은 오류가 문서화될 때 좋은 경우입니다.

모든 135 댓글

명확히 하자면 - 여기서 한 가지 아이디어는 사용자가 예외를 포착하도록 강제하는 것이 아니라 catch 절 변수의 유형을 더 잘 추론하도록 하는 것입니다.

@DanielRosenwasser
예, 사용자는 예외를 잡아야 하지 않으므로 컴파일러에서는 문제가 없습니다(런타임에 오류가 발생함).

function fn() {
    throw "error";
}

fn();

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

그러나 개발자에게 어떤 예외가 throw될 수 있는지 표현하는 방법을 제공하고(다른 라이브러리 .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 페이지 로 이동하여 다음을 읽으십시오.

구문 분석할 문자열이 유효한 JSON이 아닌 경우 SyntaxError 예외가 발생합니다.

그리고 이것은 오류가 문서화될 때 좋은 경우입니다.

그리고 이것은 오류가 문서화될 때 좋은 경우입니다.

javascript에서 SyntaxError와 Error를 구별하는 안정적인 방법이 있습니까?

  • 예, 더 많은 코드이지만 나쁜 상황이 개체에 표시되기 때문에 다른 값과 마찬가지로 처리, 폐기, 저장 또는 유효한 결과로 변환하기 위해 전달될 수 있습니다.

  • 시도를 반환하여 시도를 무시할 수 있습니다. 시도를 모나드로 볼 수 있습니다. 모나드 계산을 찾으십시오.

    function mightFail(): Tried<number, string> {
    }
    function mightFailToo(): Tried<number, string> {
        const tried = mightFail();
        if (isSuccess(tried))  { 
             return successFrom(tried.result * 2);
        } else {
             return tried;
        }
    }
    
  • 그것은 당신의 코드에 대해 충분히 표준입니다. 예외를 던지는 제3자 라이브러리에 관해서는 일반적으로 당신에게 게임 오버를 의미합니다. 왜냐하면 예외로부터 안정적으로 복구하는 것이 거의 불가능하기 때문입니다. 이유는 그것이 코드 내부 어디에서나 던져질 수 있기 때문입니다 임의의 위치에서 종료 하고 내부 상태를 불완전하거나 손상된 상태로 두는 것

  • JavaScript 런타임에서 확인된 예외에 대한 지원이 없으며 typescript만으로는 구현할 수 없습니다.

특별한 결과 케이스로 예외를 인코딩하는 것 외에는 FP 세계에서 매우 일반적인 관행입니다.

가능한 결과를 두 부분으로 나누는 반면:

  • 하나는 반품 명세서에 의해 전달되고
  • 던져서 배달되는 또 다른

어려움을 만회해 보인다

내 생각에, 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.parseSyntaxError 을 던질 수 있다는 내 요점은 함수가 throw될 수 있다는 것을 알기 위해 문서에서 함수를 찾아봐야 하고 .d.ts 에서 더 쉽게 볼 수 있다는 것입니다.
그리고 예, instanceof SyntaxError 임을 알 수 있습니다.

오류를 던지는 것과 동일한 나쁜 상황을 나타낼 수 있습니다.
Error 를 확장하는 고유한 오류 클래스를 만들고 필요한 모든 관련 데이터를 넣을 수 있습니다.
더 적은 코드로 동일한 결과를 얻을 수 있습니다.

때로는 긴 함수 호출 체인이 있고 체인의 다른 수준에서 일부 오류를 처리하고 싶을 수 있습니다.
항상 래핑된 결과(모나드)를 사용하는 것은 꽤 성가신 일입니다.
다시 말하지만, 다른 라이브러리와 기본 오류가 어쨌든 throw될 수 있으므로 결국 모나드와 try/catch를 모두 사용하게 될 수 있습니다.

나는 당신의 말에 동의하지 않습니다. 많은 경우에 던져진 오류로부터 복구할 수 있으며 언어가 당신이 그것을 더 잘 표현할 수 있다면 그렇게 하는 것이 더 쉬울 것입니다.

타이프스크립트의 많은 것들과 마찬가지로 자바스크립트의 기능 지원 부족은 문제가 되지 않습니다.
이것:

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

유형 주석 없이 자바 스크립트에서 예상대로 작동합니다.

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

모나드 변압기는 실제 PITA입니다. 꽤 자주 들어 올리기, 들어 올리기 및 선택적 달리기가 필요합니다. 최종 결과는 거의 이해할 수 없는 코드이며 필요한 진입 장벽보다 훨씬 높습니다. 모든 결합기 및 리프팅 기능(필수 boxing/unboxing 제공)은 당면한 문제에서 주의를 산만하게 하는 소음일 뿐입니다. 나는 상태, 효과 등에 대해 명시적으로 말하는 것이 좋은 것이라고 생각하지만 아직 편리한 래핑/추상화를 찾지 못했다고 생각합니다. 우리가 그것을 찾을 때까지 전통적인 프로그래밍 패턴을 지원하는 것은 실험과 탐구를 멈추지 않고 가는 방법처럼 보입니다.

추신: 맞춤 연산자보다 더 많은 것이 필요하다고 생각합니다. 고급 유형과 일종의 유형 클래스도 실용적인 모나딕 라이브러리에 필수적입니다. 그 중 저는 HKT를 먼저 평가하고 클래스를 두 번째로 평가합니다. 하지만 TypeScript는 그러한 개념을 연습하기 위한 언어가 아닙니다. 놀아요. 그렇습니다. 하지만 그 철학과 뿌리는 근본적으로 적절하고 원활한 통합을 위해 멀리 떨어져 있습니다.

OP 질문으로 돌아가기 - instanceof 는 사용하기에 위험한 연산자입니다. 그러나 명시적 예외는 Error 로 제한되지 않습니다. 자신의 ADT나 사용자 지정 POJO 오류도 던질 수 있습니다. 제안된 기능은 매우 유용할 수 있으며 물론 오용될 수도 있습니다. 어쨌든 그것은 의심할 여지 없이 좋은 기능을 더 투명하게 만듭니다. 전체적으로 저는 50/50입니다 :)

@aleksey-bykov

개발자는 여러분이 설명한 다양한 js 문제를 알고 있어야 합니다 throws 를 typescript에 추가해도 js에 새로운 것이 도입되지는 않고 typescript에 기존 js 동작을 표현할 수 있는 기능만 언어로 제공한다는 것입니다.

타사 라이브러리에서 오류가 발생한다는 사실이 정확히 제 요점입니다.
그들의 정의 파일이 그것을 포함한다면 내가 그것을 알 수 있는 방법이 있을 것입니다.

@aluanhaddad
Error 를 확장하는 것이 왜 끔찍한 패턴입니까?

@gcnew
instanceof 의 경우, 이는 단지 예일 뿐입니다. 저는 항상 유형이 다른 일반 객체를 던진 다음 유형 가드를 사용하여 이들을 구별할 수 있습니다.
어떤 유형의 오류를 던지고 싶은지는 개발자의 몫이며, 이미 그럴 수도 있지만 현재로서는 이를 표현할 방법이 없습니다. 이것이 바로 이 제안이 해결하고자 하는 것입니다.

@nitzanomer 네이티브 클래스 서브클래싱( Error , Array , RegExp 등)은 이전 ECMAScript 버전(ES6 이전)에서 지원되지 않았습니다. 이러한 클래스에 대한 하위 수준 방출은 예기치 않은 결과를 제공하며(최선의 노력을 기울였지만 이것은 가능한 한 최대입니다) 매일 기록되는 수많은 문제의 원인입니다. 경험상 - 최신 ECMAScript 버전을 대상으로 하고 실제로 무엇을 하고 있는지 알고 있지 않는 한 네이티브를 하위 분류하지 마십시오.

@gcnew
오, 나는 무엇이 잘못되었는지 알아내려고 몇 시간 이상을 보냈기 때문에 그것을 잘 알고 있습니다.
그러나 이제 그렇게 할 수 있는 능력이 있으므로 하지 않을 이유가 없어야 합니다(es6을 대상으로 할 때).

어쨌든 이 제안은 사용자가 Error 클래스를 서브클래싱하고 있다고 가정하지 않으며 단지 예일 뿐입니다.

@nitzanomer 제안이 Error 로 제한되어 있다고 주장하는 것이 아닙니다. 나는 그것을 하위 분류하는 것이 왜 나쁜 패턴인지 설명했습니다. 내 게시물에서 나는 실제로 사용자 정의 개체 또는 차별적인 공용체도 사용될 수 있다는 입장을 변호했습니다.

instanceof 는 위험하고 JavaScript의 특성을 빼더라도 안티 패턴으로 간주됩니다. 예 를 들어 instanceof 연산자를 조심하십시오 . 그 이유는 컴파일러가 새로운 하위 클래스에 의해 도입된 버그로부터 사용자를 보호할 수 없기 때문입니다. instanceof 를 사용하는 논리는 취약하고 소수의 옵션만 기대하므로 개방/폐쇄 원칙을 따르지 않습니다. 와일드카드 대소문자가 추가되더라도 새 파생물은 작성 당시의 가정을 깨뜨릴 수 있으므로 여전히 오류를 일으킬 가능성이 있습니다.

알려진 대안을 구별하려는 경우 TypeScript에는 Tagged Unions (차별화된 공용체 또는 대수 데이터 유형이라고도 함)가 있습니다. 컴파일러는 모든 경우가 처리되어 좋은 보장을 제공하는지 확인합니다. 단점은 유형에 새 항목을 추가하려는 경우 해당 유형을 식별하는 모든 코드를 살펴보고 새로 추가된 옵션을 처리해야 한다는 것입니다. 장점은 그러한 코드가 깨졌을 가능성이 가장 높지만 런타임에 실패할 수 있다는 것입니다.

나는 이 제안을 한 번 더 생각하고 반대하게 되었습니다. 그 이유는 throws 선언이 서명에 있었지만 시행되지 않은 경우 이미 문서 주석으로 처리할 수 있기 때문입니다. 시행되는 경우 JavaScript에 유형이 지정된 catch 절에 대한 Java 메커니즘이 없기 때문에 짜증이 나고 빨리 삼켜질 것이라는 감정을 공유합니다. 예외를 사용하는 것(특히 제어 흐름으로) 역시 확립된 관행이 아닙니다. 이 모든 것이 확인된 예외가 너무 적게 가져오는 반면 실패를 나타내는 더 나은 현재 일반적인 방법을 사용할 수 있다는 것을 이해하도록 이끕니다(예: 유니온 반환).

@gcnew
이것이 C#에서 수행되는 방식입니다. 문제는 문서가 typescript에서 표준이 아니라는 것입니다.
잘 문서화되어 있는 정의 파일을 본 기억이 없습니다. 다른 lib.d.ts 파일에는 주석이 포함되어 있지만 여기에는 throw된 오류가 포함되어 있지 않습니다(한 가지 예외: lib.es6.d.ts 에는 Date[Symbol.toPrimitive](hint: string) throws 하나가 있음).

또한 이 제안은 문서 주석에서 오류가 발생한 경우 발생하지 않는 오류 추론을 고려합니다. 유추된 검사 예외를 사용하면 개발자는 throws 절을 지정할 필요조차 없으며 컴파일러는 자동으로 이를 유추하여 컴파일에 사용하고 결과 정의 파일에 추가합니다.

오류 처리를 시행하는 것이 좋지 않다는 데 동의하지만 이 기능을 사용하면 원하는 사람들이 사용할 수 있는 더 많은 정보를 추가할 수 있습니다.
문제:

... 실패를 나타내는 더 좋고 현재 더 일반적인 방법이 있습니다.

그것은 그것을하는 표준 방법이 없다는 것입니다.
유니온 리턴을 사용할 수 있고 @aleksey-bykov는 Tried<> 를 사용하고 다른 타사 라이브러리의 개발자는 완전히 다른 작업을 수행합니다.
오류를 던지는 것은 여러 언어(js, java, c#...)의 표준이며 해결 방법이 아니라 시스템의 일부이기 때문에 (내 생각에는) typescript에서 더 잘 처리되어야 하며 그 증거는 숫자입니다. 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();
}

mightThrowdontCare 둘 다 $# 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();
       }
    }
}

$# mightThrowstring 를 발생시키고 해당 오류가 처리되지 않았기 때문에 $ MyClass.keepAllValuesthrows string 로 유추됩니다.

귀하의 keepAllValues 예는 무슨 말인지 잘 모르겠습니다.

mightThrow 인터럽트 keepAllValues 에서 처리되지 않은 예외가 발생하고 상태가 손상된 상태로 유지되는 작업의 중간에 완료되도록 했습니다. 문제입니다. 당신이 제안하는 것은 이 문제에 눈을 감고 심각하지 않은 척하는 것입니다. 내가 제안하는 것은 모든 확인된 예외가 즉시 처리되고 명시적으로 다시 발생하도록 요구하여 이 문제를 해결하는 것입니다. 이렇게 하면 의도하지 않게 상태가 손상될 수 있는 방법이 없습니다. 선택하면 여전히 손상될 수 있지만 의도적인 코딩이 필요합니다.

생각해 보세요. 예외를 처리할 수 있는 두 가지 방법이 있습니다.

  • 그것들을 처리하지 않으면 충돌이 발생합니다. 충돌이 원하는 것이라면 여기에서 괜찮습니다.
  • 충돌을 원하지 않는다면 어떤 종류의 예외를 찾아야 하는지에 대한 몇 가지 지침이 필요하며 이것이 제안이 들어오는 곳입니다. 확인된 예외 - 모두 명시적으로 나열되므로 모두 처리하고 수행할 수 없습니다. 아무것도 놓치지 마세요

이제 적절하게 처리되고 충돌을 방지하는 확인된 예외를 사용하기로 결정했다면 포착하는 위치의 여러 계층에서 오는 예외를 처리할 때 상황을 배제 해야 합니다.

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
그래서 당신은 모든 오류를 자바처럼 처리해야 한다고 제안합니까?
나는 js/ts가 훨씬 더 역동적이고 사용자들이 그것에 익숙하기 때문에 (자바에서 왔고 여전히 그것을 사랑하지만) 그 팬이 아닙니다.
컴파일할 때 포함하면 오류를 처리하는 플래그가 될 수 있습니다(예 strictNullChecks ).

내 제안은 처리되지 않은 예외를 해결하기 위해 여기에 있는 것이 아닙니다. 게시한 코드는 이 기능이 구현되지 않으면 이제 중단될 것이며 js에서도 중단될 것입니다.
내 제안은 개발자로서 당신이 던질 수 있는 다양한 오류를 더 잘 알 수 있도록 하며, 처리할지 무시할지 여부는 여전히 당신에게 달려 있습니다.

0으로 나누기 문제의 경우 오류가 발생하지 않습니다.

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

발생할 수 있는 다양한 오류에 대해 더 잘 알고 있습니다.

그들이 처리할 수 없다면 그렇게 할 의미가 없습니다. 현재 제안은 내가 나열한 사례 때문에 실행 가능하지 않습니다.

그래서 당신은 모든 오류를 자바처럼 처리해야 한다고 제안합니까?

예, 이것이 예외를 확인했다는 의미입니다.

@aleksey-bykov
나는 당신이 열거한 어떤 경우가 이 제안을 실행 불가능한 것으로 만드는 이유를 알지 못합니다.

DivisionByZero 를 던진 것으로 추론된 함수를 사용하는 경우에도 호출 체인 아래로 던져진 오류를 처리하는 데 문제가 없습니다. .
다른 인수를 사용하여 다시 시도할 수 있고, 사용자에게 문제가 발생했다는 메시지를 표시할 수 있으며, 이 문제를 기록할 수 있으므로 나중에 문제를 처리하도록 코드를 변경할 수 있습니다(자주 발생하는 경우).

다시 말하지만, 이 제안은 런타임에 아무 것도 변경하지 않으므로 작동한 모든 것이 이전과 같이 계속 작동합니다.
유일한 차이점은 던질 수 있는 오류에 대한 더 많은 정보가 있다는 것입니다.

나는 당신이 말하는 것을 알지만 자바스크립트 런타임에는 아무 것도 변경되지 않을 것입니다. 그러나 여기에서 당신의 메시지는 사용자에게 다음과 같은 확신을 가지고 아래 20개 레이어에서 발생한 예외를 처리함으로써 자신이 무엇을 하고 있는지 알고 있다는 환상을 주기 위한 것입니다. 그들은 즉각적인 예외를 처리할 것입니다

20층 아래에서 발생한 문제를 해결할 수 있는 방법은 없습니다.

확인되지 않은 예외와 마찬가지로 기록할 수 있지만 수정할 수는 없습니다.

그래서 일반적으로 거짓말입니다 TS에는 충분한 거짓말이 있습니다 사람들을 더 혼란스럽게하지 마십시오

@aleksey-bykov
당신이 설명하는 것은 예외를 지원하는 모든 언어로 존재합니다.
아무도 예외를 잡는 것이 문제를 해결한다고 말하지는 않았지만 그것을 우아하게 처리할 수 있을 것입니다.

함수를 호출할 때 어떤 오류가 발생할 수 있는지 알면 개발자가 처리할 수 있는 오류와 처리할 수 없는 오류를 구분하는 데 도움이 됩니다.

지금 개발자는 JSON.parse 를 사용하면 오류가 발생할 수 있다는 사실을 모를 수 있지만 lib.d.ts 의 일부이고 IDE에서 (예를 들어) 알려주면 다음을 선택할 수 있습니다. 이 사건을 처리하십시오.

내부 상태가 19개 계층에서 손상되었고 상태가 비공개이기 때문에 갈 수 없기 때문에 20개 계층 아래에서 발생한 문제를 정상적으로 처리할 수 없습니다.

건설적인 것: 내가 제안하는 것은 사용자가 확인된 예외를 즉시 처리하고 명시적으로 다시 던지도록 요구하는 것입니다. 이렇게 하면 의도하지 않은 혼동을 배제하고 확인되지 않은 예외를 분리합니다.

  • 확인된 예외: 즉각적인 도달 범위에서 발생했으며 처리해야 합니다. 이렇게 하면 상태가 손상되지 않고 계속 진행하는 것이 안전합니다.
  • 확인되지 않은 예외: 즉시 도달하거나 훨씬 더 아래에서 발생했습니다. 상태가 손상되었기 때문에 처리할 수 없습니다. 기록하거나 스스로 위험을 감수할 수 있습니다.

$#$ JSON.parse #$의 SyntaxError 는 확인된 예외로 선언되어야 합니다.

@aleksey-bykov

왜 개발자들이 원하지 않는 일, 지금까지 하지 않은 일을 하도록 강제할 필요가 있는지 모르겠습니다.

다음은 예입니다.
사용자가 json 데이터를 작성/붙여넣기한 다음 버튼을 클릭할 수 있는 웹 클라이언트가 있습니다.
앱은 이 입력을 받아 어떻게든 이 json을 구문 분석하고 다양한 유형의 값(문자열, 숫자, 부울, 배열 등)과 함께 json을 반환하는 타사 라이브러리에 전달합니다.
이 타사 라이브러리에서 SyntaxError 가 발생하면 복구할 수 있습니다. 사용자에게 입력이 잘못되었으며 다시 시도해야 한다고 알립니다.

함수를 호출할 때 어떤 오류가 발생할 수 있는지 알면 개발자는 처리할 수 있는 항목과 처리하지 않는 항목을 결정할 수 있습니다.
오류가 발생한 사슬의 깊이는 중요하지 않습니다.

당신은 내가 말하는 것을 이해하지 못하는 것 같습니다. 우리는 원을 그리며 가고 있습니다

타사 라이브러리에서 SyntaxError 를 던짐으로써 캡슐화되어야 하는 자체 코드의 구현 세부 정보에 사용자를 노출시킵니다.

기본적으로 당신은 말하죠, 이봐, 작동하지 않는 것은 내 코드가 아니라, 내가 인터넷에서 찾아서 사용한 그 멍청한 라이브러리입니다. 따라서 문제가 있으면 내가 아닌 제3자 라이브러리를 처리하십시오. 내가 요청한 것을 말했을 뿐이야

그리고 해당 SyntaxError 이후에 해당 3번째 lib의 인스턴스를 계속 사용할 수 있다는 보장은 없습니다. 사용자에게 보장을 제공하는 것은 사용자의 책임입니다.

결론적으로, 내부 예외 처리를 담당해야 합니다( 모두가 아니라 확인된 예외만 처리).

나는 당신이 말하는 것을 이해하지만 그것에 동의하지 않습니다.
당신 말이 맞아요, 그게 기본적으로 제가 하는 말이에요.
오류가 발생하는 타사 라이브러리를 사용한 경우 이를 처리하거나 무시하고 내 코드 사용자가 처리하도록 할 수 있습니다.
그렇게 하는 데에는 여러 가지 이유가 있습니다. 예를 들어 내가 작성하는 라이브러리는 UI에 구애받지 않으므로 사용자에게 뭔가 잘못되었다고 알릴 수는 없지만 내 라이브러리를 사용하는 사람은 사용할 때 발생하는 오류를 처리할 수 있습니다. 내 lib를 만들고 사용자와 상호 작용하여 처리합니다.

라이브러리가 던질 때 손상된 상태로 남아 있으면 문서화해야 할 것입니다.
그런 다음 그러한 라이브러리를 사용하고 결과적으로 오류가 발생하면 내 상태가 손상되면 문서화해야 합니다.

결론:
이 제안은 throw된 오류에 대한 추가 정보를 제공하기 위해 제공됩니다.
개발자에게 다른 일을 하도록 강요해서는 안 되며, 원할 경우 오류를 더 쉽게 처리할 수 있도록 하십시오.

동의하지 않으셔도 됩니다. 괜찮습니다. 확인된 예외라고 부르지 맙시다.

개발자가 이를 인식하도록 하는 것이 중요하므로 나열된 예외 또는 공개된 예외라고 합시다.

@aleksey-bykov
공평하게, 이름이 변경되었습니다.

@aleksey-bykov

내부 상태가 19개 계층에서 손상되었고 상태가 비공개이기 때문에 갈 수 없기 때문에 20개 계층 아래에서 발생한 문제를 정상적으로 처리할 수 없습니다.

아니요, 내부 상태를 고칠 수는 없지만 로컬 상태는 확실히 고칠 수 있으며, 스택의 더 깊은 곳이 아니라 여기에서 처리해야 하는 요점입니다.

예외를 처리할 때 일부 공유 변경 가능한 값이 어떤 상태에 있는지 확신할 방법이 없다는 주장이 있다면 이는 명령형 프로그래밍에 대한 주장이며 이 제안에 국한되지 않습니다.

모든 계층이 아래 계층에서 즉시 발생하는 예외에 대한 책임을 져야 하는 경우 성공적인 복구를 위한 훨씬 더 나은 기회가 있습니다.

다른 말로 하자면, 아래의 1개 이상의 레벨에서 오는 예외는 하나의 문장입니다. 모든 인프라를 처음부터 다시 인스턴스화하는 것 외에는 아무것도 하기에는 너무 늦었습니다(운이 좋다면 할 수 있는 전역 잔여물이 없습니다.) 도달)

언급된 제안은 대부분 쓸모가 없습니다. 왜냐하면 당신이 도달할 수 없는 범위에서 나쁜 일이 발생했다는 사실에 대해 믿을만한 방법이 없기 때문입니다.

이것은 훌륭합니다. FWIW: 추가되면 기본적으로 던지는 메서드를 처리하거나 메서드를 던지는 것으로 표시해야 한다고 생각합니다. 그렇지 않으면 거의 문서 일뿐입니다.

@agonzalezjr
typescript의 대부분의 기능과 마찬가지로 이 기능도 선택할 수 있어야 한다고 생각합니다.
유형을 추가하는 것이 필수가 아닌 것처럼 던지거나 잡아야 하는 것도 아닙니다.

--onlyCheckedExceptions 와 같이 필수로 만드는 플래그가 있어야 합니다.

어떤 경우에도 이 기능은 문서화뿐만 아니라 throw된 예외 유형을 추론/검증하는 데에도 사용됩니다.

@nitzanomer

다음은 예입니다.
사용자가 json 데이터를 작성/붙여넣기한 다음 버튼을 클릭할 수 있는 웹 클라이언트가 있습니다.
앱은 이 입력을 받아 어떻게든 이 json을 구문 분석하고 다양한 유형의 값(문자열, 숫자, 부울, 배열 등)과 함께 json을 반환하는 타사 라이브러리에 전달합니다.
이 타사 라이브러리에서 SyntaxError가 발생하면 복구할 수 있습니다. 사용자에게 입력이 잘못되었으며 다시 시도해야 한다고 알립니다.

이것은 확실히 확인된 예외에 대한 전체 개념이 모호해지는 영역 중 하나입니다. _예외적 상황_의 정의가 불분명해지는 곳이기도 하다.
귀하의 예에서 프로그램은 확인된 예외를 던지는 것으로 선언되는 JSON.parse 에 대한 인수가 될 것입니다.
그러나 프로그램이 HTTP 클라이언트이고 잘못된 형식의 본문을 포함하는 HTTP 응답에 첨부된 헤더 값을 기반으로 JSON.parse 를 호출하는 경우에는 어떻게 될까요? 프로그램이 복구하기 위해 할 수 있는 의미 있는 것은 없으며 할 수 있는 것은 다시 던지는 것뿐입니다.
이것은 JSON.parse 가 확인된 것으로 선언되는 것에 대한 인수라고 말하고 싶습니다.

그것은 모두 사용 사례에 달려 있습니다.

나는 당신이 이것이 깃발 아래 있다고 제안한다는 것을 이해하지만 깃발을 활성화하기 위해 이 기능을 사용하고 싶다고 상상해 봅시다. 어떤 프로그램을 작성하느냐에 따라 도움이 될 수도, 방해가 될 수도 있습니다.

고전적인 java.io.FileNotFoundException 도 이에 대한 예입니다. 확인했는데 프로그램이 복구될 수 있나요? 그것은 정말로 누락된 파일이 호출자가 아닌 호출자에게 무엇을 의미하는지에 달려 있습니다.

@aluanhaddad

이 제안은 새로운 기능을 추가할 것을 제안하지 않고, 자바스크립트에 이미 존재하는 것을 타이프스크립트로 표현하는 방법을 추가하기 위한 것입니다.
오류가 발생하지만 현재 typescript는 오류를 선언할 방법이 없습니다(던지거나 잡을 때).

귀하의 예를 들어, 오류를 포착함으로써 프로그램은 이 오류를 포착함으로써 "정상적으로" 실패할 수 있거나(예를 들어 사용자에게 "무언가 잘못되었습니다" 메시지를 표시함) 프로그램/개발자에 따라 이를 무시할 수 있습니다.
프로그램의 상태가 이 오류의 영향을 받을 수 있는 경우 이를 처리하면 손상된 상태 대신 유효한 상태를 유지할 수 있습니다.

어쨌든 개발자는 발생한 오류에서 복구할 수 있는지 여부를 확인해야 합니다.
복구의 의미를 결정하는 것도 그에게 달려 있습니다. 예를 들어 이 http 클라이언트를 타사 라이브러리로 사용하는 경우 라이브러리에서 발생하는 모든 오류가 같은 유형이 되기를 원할 수 있습니다.

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

이제 내 라이브러리에서 JSON.parse 를 사용하여 응답을 구문 분석할 때 발생한 오류를 포착한 다음 내 자신의 오류를 발생시키고 싶습니다.

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

이 기능이 구현되면 이 동작을 쉽게 선언할 수 있고 내 라이브러리 사용자에게 이 기능이 어떻게 작동하고 실패하는지 명확하게 알 수 있습니다.

이 제안은 새로운 기능을 추가할 것을 제안하지 않고, 자바스크립트에 이미 존재하는 것을 타이프스크립트로 표현하는 방법을 추가하기 위한 것입니다.

알아요. 이 제안에 따라 TypeScript가 발생시키는 오류에 대해 이야기하고 있습니다.
내 가정은 이 제안이 확인된 예외 지정자와 확인되지 않은 예외 지정자(유추 또는 명시적) 사이의 구별을 암시하며 다시 형식 확인 목적으로만 사용된다는 것입니다.

@aluanhaddad

이전 댓글에서 말씀하신 내용:

그러나 프로그램이 HTTP 클라이언트이고 잘못된 형식의 본문이 포함된 HTTP 응답에 첨부된 헤더 값을 기반으로 JSON.parse를 호출하는 경우에는 어떻게 될까요? 프로그램이 복구하기 위해 할 수 있는 의미 있는 것은 없으며 할 수 있는 것은 다시 던지는 것뿐입니다.

내 함수가 결과를 반환하도록 선언된 경우 null 를 반환하는 경우에도 동일하게 적용됩니다.
개발자가 strictNullChecks 를 사용하기로 선택한 경우 함수가 (던지는 대신) null 를 반환하고 동일한 시나리오에서 "프로그램이 할 수 있는 의미 있는 것은 없습니다. 회복".

그러나 onlyCheckedExceptions 플래그를 사용하지 않더라도 이 기능은 여전히 ​​유용합니다. 예를 들어 함수가 Error 만 던지도록 선언되었을 때 오류를 string 로 잡아내려고 하면 컴파일러가 불평할 것이기 때문입니다. Error .

좋은 아이디어입니다. 중첩된 호출이 당신에게 무엇을 던질지 알 방법이 없기 때문에 그것은 도움이 될 것이지만 엄격하거나 안전하지 않습니다.

의미, 유형 A의 예외를 던질 수 있는 함수가 있지만 내부에서 중첩 함수를 호출하고 이를 try catch에 넣지 않으면 호출자에게 유형 B 예외를 던질 것입니다.
따라서 호출자가 유형 A 예외만 예상하면 중첩된 예외에서 다른 유형을 가져오지 않는다는 보장이 없습니다.

(쓰레드가 너무 길어서 이 댓글을 놓쳤다면 죄송합니다)

@shaipetel
명제는 컴파일러가 처리되지 않은 오류의 유형을 유추하고 이를 함수/메서드 서명에 추가한다고 명시합니다.
따라서 설명한 경우 B 가 처리되지 않은 경우 함수에서 A | B 가 발생합니다.

알 겠어요. 내가 호출한 모든 메서드를 드릴다운하고 가능한 모든 예외 유형을 수집합니까?
가능하다면 그렇게 되었으면 좋겠습니다. 개발자는 항상 선언되지 않은 예기치 않은 예외가 있을 수 있습니다. 이 경우 "객체가 인스턴스로 설정되지 않음" 또는 "0으로 나누기" 또는 유사한 예외가 거의 모든 함수에서 항상 가능합니다.
IMHO, 모든 예외가 메시지가 있는 기본 클래스에서 상속되고 래핑되지 않은 텍스트 또는 기타 개체를 던지는 것을 전혀 허용하지 않는 C#에서와 같이 가장 잘 처리되었을 것입니다. 기본 클래스와 상속이 있는 경우 캐치를 계단식으로 배열하고 한 블록에서는 예상 오류를 처리하고 다른 블록에서는 예상치 못한 오류를 처리할 수 있습니다.

@shaipetel
자바스크립트에서 모든 오류는 Error 클래스를 기반으로 하지만 오류를 던지는 것에 국한되지 않고 무엇이든 던질 수 있습니다.

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

예, JavaScript가 작동하는 방식을 알고 있습니다. 우리는 추가 제한을 부과하는 TypeScript에 대해 논의하고 있습니다.
좋은 솔루션 IMHO는 모든 throw된 예외가 특정 기본 클래스에 속하고 래핑되지 않은 값을 직접 throw할 수 없도록 하는 예외 처리를 TypeScript가 따르도록 하는 것입니다.

따라서 "throw 0" 또는 "throw 'some error'"를 허용하지 않습니다.
JavaScript가 TypeScript가 허용하지 않는 많은 것을 허용하는 것처럼.

감사 해요,

@nitzantomer

내 함수가 결과를 반환하도록 선언된 경우 null을 반환하는 경우에도 동일하게 적용됩니다.
개발자가 strictNullChecks를 사용하기로 선택한 경우 함수가 null을 반환하는 경우(던지는 대신) 동일한 시나리오에서 "프로그램이 복구를 위해 수행할 수 있는 의미 있는 것이 없습니다"라고 정확히 말할 수 있습니다.

그러나 onlyCheckedExceptions 플래그를 사용하지 않더라도 이 기능은 여전히 ​​유용합니다. 예를 들어 함수가 오류만 발생하도록 선언된 경우 오류를 문자열로 catch하려고 하면 컴파일러에서 불평할 것이기 때문입니다.

나는 당신이 말하는 것을 이해합니다.

@shaipetel 이 제안 및 다른 곳에서 이전에 논의한 바와 같이 ErrorArray $와 같은 내장 함수를 서브클래싱하는 것은 작동하지 않습니다. 이는 널리 사용되는 런타임 및 컴파일 대상 간에 다른 동작으로 이어집니다.

다양한 비 오류 유형의 값을 잡는 것은 제안된 기능을 구상하는 가장 실행 가능한 방법입니다. 실제로 이 기능을 활용하는 예외 전파 메커니즘이 스택 추적 또는 기타 오류 관련 속성을 말하는 것보다 훨씬 더 구체적이고 훨씬 더 유용한 오류가 발생할 가능성이 있다는 문제로 보지 않습니다.
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); }

@grofit
typescript가 당신이 제안하는 것을 지원한다면 좋겠지만 제 생각에는 이 문제와 관련이 없습니다.
당신이 제안하는 것은 이 문제가 무엇인지 구현하지 않고도 구현할 수 있습니다. 단지 컴파일러는 NotFoundError 가 발생하지 않는다고 불평할 수 없을 것입니다.

나는 이것이 어떤 종류의 문제가 아니라 형식화 된 캐치에 대한 제안이라고 생각했습니다. 스레드를 복제하고 싶지 않았기 때문에 자체 문제에 게시할 것입니다.

@grofit
Typed catch는 기능 요청의 일부일 뿐만 아니라 컴파일러에게 함수에서 어떤 유형의 오류가 발생할 수 있는지 알려주는 기능이기도 합니다. 그러면 컴파일러는 어떤 유형의 오류가 발생할 수 있는지 추론할 수도 있습니다.

제 생각에는 다른 부분 없이 형식화된 캐치를 구현할 수 있습니다. 새 문제를 열면 TS 팀이 해당 문제를 중복으로 표시하기로 결정할 수도 있습니다. 저는 잘 모르겠습니다.

@grofit

이것은 이전 비트를 출력하지만 구문 설탕으로 제네릭으로 할 수도 있지만 약간 더 나쁩니다.

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 확장을 권장하기 때문에 유독합니다. 이는 매우 나쁜 일입니다.

@nitzanomer 나는 그것이 별개의 문제라는 데 동의합니다. OP에는 Error 를 확장하지 않는 catch 유형의 예가 있습니다.

@aluanhaddad
@grofit 이 요구하는 사항에 대해 다음과 같이 할 수 있습니다.

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

여기서 isXXX(error) 는 다음 형식의 유형 가드입니다.
function isXXX(error): error is XXX { ... }

@nitzanomer는 확실하지만 유형 가드가 문제가 아닙니다. 문제는

class MyError extends Error {
  myErrorInfo: string;
}

문제가 있지만 여기에서 이미 논의되었습니다. 요점을 자세히 설명하고 싶지는 않지만 이 잘못된 관행을 채택함으로써 많은 크고 잘 알려진 코드 기반이 부정적인 영향을 받았습니다. Error 를 확장하는 것은 나쁜 생각입니다.

@aluanhaddad
나는 그것을 알고 있습니다. 이것이 @grofit 이 요청한 것을 달성할 수 있는 방법을 제안했지만 Error 을 확장할 필요가 없는 이유입니다. 개발자는 원한다면 Error 를 계속 확장할 수 있지만 컴파일러는 instanceof 를 사용할 필요가 없는 js 코드를 생성할 수 있습니다.

@nitzanomer 당신이 알고 있다는 것을 알고 있지만 좋은 아이디어처럼 들리는 @grofit 에 제안한 내용을 깨닫지 못했습니다.

나는 그런 패턴을 사용하게 하려는 API를 처리해야 하는 것이 싫기 때문에 죽은 말을 이기고 있습니다. 여하튼, 제가 이 토론을 주제에서 벗어났다면 죄송합니다.

이 토론에 대해 더 이상 생각이 없었습니까? typescript에서 throws 절을 보고 싶습니다.

@aluanhaddad Error 확장은 나쁜 습관이라고 계속 말씀하시네요. 이것에 대해 조금 더 자세히 설명해 주시겠습니까?

그것은 길게 논의되었습니다. 기본적으로 내장 유형을 안정적으로 확장할 수 없습니다. 동작은 환경과 --target 설정의 조합에 따라 크게 달라집니다.

내가 질문한 유일한 이유는 당신의 의견이 내가 이것에 대해 처음 들었기 때문입니다. 약간의 인터넷 검색 끝에 Error 확장해야 하는 방법과 이유를 설명하는 기사만 찾았습니다.

아니오, 작동하지 않습니다. https://github.com/Microsoft/TypeScript/issues/12123 및 연결된 수많은 문제에서 이에 대해 자세히 읽을 수 있습니다.

그것은 작동하지만 ES6이 적응되면서 대다수가 되고 있는 "특정 환경"에서만 작동합니다.

@nitzanomer : try/catch 주위에 @throws 가 있는 함수가 호출될 때 개발자에게 알려주는 TSLint 검사를 구현하는 것이 가능하다고 생각하십니까?

@bennyn 저는 TSLint 사용자이지만 새로운 규칙을 만드는 방법을 살펴 본 적이 없습니다.
나는 그것이 가능한지(내 생각에 그것이 가능하다고 생각하지만), 만약 그렇다면 얼마나 쉬운지 모릅니다.

당신이 그것을 시도한다면, 여기에서 업데이트하십시오, 감사합니다.

@shaipetel (또한 @nitzanomer)

답장:

예, JavaScript가 작동하는 방식을 알고 있습니다. 우리는 추가 제한을 부과하는 TypeScript에 대해 논의하고 있습니다.
좋은 솔루션 IMHO는 모든 throw된 예외가 특정 기본 클래스에 속하고 래핑되지 않은 값을 직접 throw할 수 없도록 하는 예외 처리를 TypeScript가 따르도록 하는 것입니다.
따라서 "throw 0" 또는 "throw 'some error'"를 허용하지 않습니다.
JavaScript가 TypeScript가 허용하지 않는 많은 것을 허용하는 것처럼.

이것이 @shaipetel 을 제안한 것인지 확실하지 않지만 만일을 대비하여... Typescript가 throwError 만 반환하도록 제한하지 않도록 주의하겠습니다. React의 곧 출시될 Async Rendering 기능은 throw ing Promise 으로 작동합니다! (이상하게 들리겠지만... 하지만 나는 그들이 사용 사례에 대해 async / awaityield / yield* 생성기와 비교하여 이것을 평가했다는 것을 읽었습니다. 그리고 나는 그들이 무엇을 하고 있는지 확실히 알고 있습니다!)

throw ing Error s 는 99%의 사용 사례가 있다고 확신하지만 100%는 아닙니다. Typescript가 Error 기본 클래스를 확장하는 것을 throw 로 제한해서는 안 된다고 생각합니다. 확실히 훨씬 더 고급이지만 throw 에는 오류/예외 이외의 다른 사용 사례가 있습니다.

@mikeleonard
다양한 유형을 발생시키는 기존 js 코드의 많은 예가 있다는 데 동의합니다(많은 throw "error message string" 를 보았습니다).
새로운 React 기능은 또 다른 좋은 예입니다.

내 2 센트, 나는 코드를 비동기/대기로 변환하고 있으므로 던질 수 있습니다 (제쳐두고, 나는 예외를 싫어합니다). 내 의견으로는 이 문제에 대해 논의하는 것처럼 throws 절이 있으면 좋을 것입니다. "던짐"이 허용되더라도 유용할 것이라고 생각합니다. (또한 "nothrows"를 기본값으로 하는 컴파일러 옵션과 "nothrows"가 있을 수 있습니다.)

함수가 던지는 것을 입력할 수 있게 해주는 자연스러운 확장처럼 보입니다. 반환 값은 TS에서 선택적으로 입력할 수 있으며 내게는 throw가 또 다른 유형의 반환인 것처럼 느껴집니다(https://stackoverflow.com/a/39209039/162530과 같이 throw를 피하기 위해 제안된 모든 대안에서 입증됨).

(개인적으로 나는 또한 throws로 선언된 함수에 대한 호출자가 throws로 선언하거나 catch해야 하도록 강제하는 (선택 사항) 컴파일러 옵션을 갖고 싶습니다.)

현재 사용 사례: 전체 [Angular] 코드 기반을 오류 처리에 예외를 사용하도록 변환하고 싶지 않습니다(내가 싫어하기 때문에). 내 API의 구현 세부 정보에서 async/await를 사용하지만 API가 반환되면 throw를 일반 Promises/Observable로 변환합니다. 컴파일러가 내가 올바른 것을 포착하고 있는지(또는 이상적으로는 전혀 포착하고 있는지) 확인하게 하는 것이 좋을 것입니다.

@aleksey-bykov 자바스크립트의 본질을 바꾸지 말고 그냥 타이핑만 추가하자. :)

입력을 추가하면 이미 변경됩니다(이치에 맞지 않는 코드는 잘라냄).
같은 방법으로 예외 처리를 강화할 수 있습니다.

2018년 7월 26일 목요일 오후 8:22 Joe Pea [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
.

약속의 경우 async/await 대신 then 연결을 사용하고 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() 를 반환하면 유형이 추가됩니다. 제대로 작동하지 않는 유일한 것은 공용체 매개변수 유형의 추론입니다(아마도 버그일 것입니다)

위의 예에서 해결된 문제:

  • 오류에 국한되지 않고 무엇이든 거부할 수 있습니다(아마도 나쁜 생각일 수 있음)
  • 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

모든 현재 코드가 컴파일되도록 보장합니다.

null 반환 값이 매우 드문 strictNullChecks 와 달리 JS의 예외는 꽤 만연하다고 생각합니다. .d.ts 파일로 모델링하는 것은 그리 나쁘지 않을 수 있지만(오류를 설명하기 위해 종속성에서 유형을 가져옴) 확실히 쉬운 일이 아니며 결과적으로 거대한 통합이 발생합니다.

Promise는 이미 래퍼이고 비동기 코드는 일반적인 시나리오에서 오류 처리가 가장 많이 분기되는 곳이므로 좋은 중간 지점은 Promises 및 async/await 에 초점을 맞추는 것입니다. 다른 오류는 "확인되지 않음"입니다.

@spion-h4 이것은 (이미/의지) typescript에서 사용할 수 있습니까?

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

@bluelovers
아니요, typescript는 현재 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 코드베이스에서 오류 처리는 가장 큰 문제 중 하나입니다. 어떤 오류 함수가 _던지고 _might_ 처리하고 싶은지 확인하는 것은 엄청나게 어렵습니다. 이는 좋은 문서를 읽고 작성하거나(우리 모두가 이 /s를 잘 알고 있음) 구현을 살펴봐야만 가능하며 반환 값과 반대로 예외는 함수 호출을 통해 자동으로 전파되기 때문에 단순히 직접 호출된 함수를 확인하지만 모두 잡으려면 중첩된 함수 호출도 확인해야 합니다. _실제 문제입니다_.

JavaScript의 오류는 실제로 매우 유용한 값입니다. NodeJS의 많은 API는 잘 정의된 오류 코드가 있고 유용한 메타데이터를 노출하는 자세한 오류 개체를 발생시킵니다. 예를 들어 child_process.execFile() 의 오류에는 exitCodestderr $와 같은 속성이 있고 $ fs.readFile() 의 오류에는 ENOENT (파일을 찾을 수 없음)와 같은 오류 코드가 있습니다. ) 또는 EPERM (권한 부족). 이 작업을 수행하는 라이브러리도 많이 알고 있습니다. 예를 들어 pg 데이터베이스 드라이버는 INSERT 실패의 원인이 된 정확한 열 제약 조건을 알기 위해 오류에 대한 충분한 메타데이터를 제공합니다.

사람들은 오류에 적절한 오류 코드가 있고 무엇인지 알지 못하기 때문에 코드베이스의 오류 메시지에 대해 걱정스러운 양의 불안정한 정규식 검사를 볼 수 있습니다.

@types 선언과 lib.d.ts 에서 이러한 함수가 던질 수 있는 오류를 정의할 수 있다면 TypeScript는 오류의 구조, 즉 가능한 오류가 무엇인지, 오류 코드 값은 어떤 속성을 가지고 있는지입니다. 이것은 형식화된 예외에 대한 것이 _아닙니다_ 따라서 형식화된 예외와 관련된 모든 문제를 완전히 방지합니다. 이것은 _실제 문제_에 대한 _실용적 솔루션_입니다(사람들에게 모나딕 오류 반환 값을 대신 사용하라고 말하는 것과 반대로 - JavaScript 영역의 현실은 다르게 보이고 함수는 오류를 던짐).

주석은 완전히 선택 사항일 수 있습니다(또는 컴파일러 플래그를 사용하여 필수 항목으로 만들 수 있음). 선언 파일에 지정되지 않은 경우 함수는 단순히 any (또는 unknown )를 발생시킵니다.
함수는 throws never 를 사용하여 throw하지 않도록 수동으로 선언할 수 있습니다.
구현이 가능한 경우, 함수는 호출하는 함수의 모든 예외 유형과 $# catch 가 있는 try 블록 내부에 없는 자체 throw 문을 통합합니다. catch 절.
그들 중 하나가 any 를 던지면 함수도 any 를 던집니다. 이것은 괜찮습니다. 모든 함수 경계에서 개발자는 명시적 주석을 통해 이를 수정할 기회가 있습니다.
그리고 대부분의 경우 잘 알려진 단일 함수가 호출되어 try/catch로 래핑되는 경우(예: 파일을 읽고 처리하지 못하는 경우) TypeScript는 catch 절의 유형을 유추할 수 있습니다.

이것에 대한 오류 서브클래싱도 필요하지 않습니다 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 })
    }
}

오류 외에도 다음과 같은 다른 종류의 부작용을 표시할 수도 있습니까?

  • 발산(무한 루프/돌아오지 않음)
  • IO
  • 비결정론( Math.random )

그것은 반환 유형에 효과를 태그할 수 있는 MSR의 Koka 를 생각나게 합니다.

제안:

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 코드를 다시 보내야 합니다. 그렇게 하려면 처리해야 하는 예외를 찾기 위해 모든 함수 호출을 항상 확인해야 합니다. 그리고 이것은 재미가 없습니다 -_-

추신. 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
}
...

나는 여기에 2센트를 주고 있지만 &! 는 나에게 엄청나게 못생겼습니다.
타이프스크립트를 처음 접하는 사람들은 이 기호에 대해 의아해할 것이라고 생각합니다. 정말 어울리지 않습니다.
내 생각에는 단순한 throws 가 더 명확하고 직관적이며 간단합니다.

그 외에는 유형이 지정된 예외의 경우 +1입니다. 👍

유형 및 확인된 예외에 대해 내 +1을 추가하고 싶습니다.
내 코드에서 형식화된 예외를 잘 활용했습니다. 나는 instanceof 의 함정을 완전히 알고 있습니다. 실제로 공통 기본 클래스에서 상속되는 관련 오류에 대한 일반 처리기를 작성할 수 있어 이점을 활용했습니다. 기본 Error 클래스에서 상속을 피하기 위해 내가 만난 대부분의 다른 방법은 (적어도 ) 똑같이 복잡하고 다른 방식으로 문제가 됩니다.
확인된 예외는 내 생각에 코드베이스에 대한 더 큰 정적 분석 통찰력을 제공하는 개선 사항입니다. 충분히 복잡한 코드베이스에서는 특정 함수에서 어떤 예외가 발생했는지 놓치기 쉽습니다.

typescript에서 컴파일 시간 오류 안전성을 찾는 사람들을 위해 내 ts-results 라이브러리를 사용할 수 있습니다.

@vultix 불행히도 팀 설정에 넣을 만큼 분리가 명확하지 않습니다.

@vultix lib의 접근 방식은 위에서 논의되었으며 이 기능이 해결하려는 것과 정반대입니다.
Javascript에는 이미 예외라고 하는 오류 처리 메커니즘이 있지만 typescript에는 이를 설명할 방법이 없습니다.

@nitzanomer 저는 이 기능이 타이프스크립트에 필수라는 데 전적으로 동의합니다. 내 라이브러리는 이 기능이 추가될 때까지 임시 대기 상태일 뿐입니다.

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

나는 이것에 대해이 게임에 조금 늦었다는 것을 알고 있지만 아래는 쉼표보다 Typescripty 구문이 조금 더 많은 것 같습니다.

Iterable<number> throws TypeError | RangeError

하지만 나는 여전히 쉼표를 잘 사용합니다. 우리가 이것을 언어로 가지고 있기를 바랄 뿐입니다.
주된 이유는 많은 동료들이 JSON.parse 에 오류가 발생할 수 있다는 사실을 잊은 것 같기 때문에 JSON.parse() 를 사용하고 싶습니다. 풀 리퀘스트.

@웜스
완전히 동의 해.
내 제안에는 다음과 같은 권장 구문이 포함되어 있습니다.

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

Java와 같은 OOP 언어의 오류 선언 패턴을 따를 수 있습니까? "throws" 키워드는 잠재적인 오류가 있는 함수를 사용할 때 "try..catch"가 필요하다고 선언하는 데 사용됩니다.

@allicanseenow
제안은 throw 절의 선택적 사용에 대한 것입니다.
즉, throw하는 함수를 사용하는 경우 try/catch를 사용할 필요가 없습니다.

@allicanseenow 링크된 문서를 포함하여 위의 내 글을 읽고 싶을 수도 있습니다. https://github.com/microsoft/TypeScript/issues/13219#issuecomment -416001890

이것이 typescript 유형 시스템에서 누락된 것 중 하나라고 생각합니다.
순전히 선택 사항이며 방출된 코드를 변경하지 않으며 라이브러리 및 기본 기능 작업에 도움이 됩니다.

또한 어떤 오류가 발생할 수 있는지 알면 편집자가 모든 오류를 처리하는 if 조건으로 catch 절을 "자동 완성"할 수 있습니다.

내 말은, 정말 작은 응용 프로그램이라도 적절한 오류 처리가 필요합니다. 지금 최악의 상황은 사용자가 언제 문제가 발생할지 모른다는 것입니다.

저에게 이것은 이제 TOP 누락 기능입니다.

@RyanCavanaugh , "추가 피드백 대기 중" 레이블이 추가된 이후로 많은 피드백이 있었습니다. 타이프스크립트 팀원이 참여할 수 있나요?

@nitzanomer TypeScript 에 throw를 추가하는 아이디어를 지원하지만 중첩된 함수에서 사용할 때 잠재적으로 부정확한 throw 선언을 허용하지 않는 try/catch 옵션이 있습니까?

개발자가 메서드의 throws 절이 정확하다고 신뢰하려면 해당 메서드의 호출 계층 구조에서 무시되고 보고되지 않은 스택 위로 던져진 선택적 예외가 없다는 위험한 가정을 해야 합니다. 나는 @ajxs 가 그의 논평 끝에 그것을 암시했을 수도 있다고 생각하지만 이것이 큰 문제가 될 것이라고 생각합니다. 특히 대부분의 npm 라이브러리가 얼마나 단편화되어 있는지.

@ConnorSinnott
내가 당신을 올바르게 이해하기를 바랍니다.

컴파일러는 다음과 같이 throw된 유형을 추론합니다.

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 string | MyError $를 던진다고 불평해야 합니다.

이 모든 것을 염두에 두고, 나는 이 가정이 다른 라이브러리, 프레임워크 등을 사용할 때 개발자가 신뢰하는 다른 선언된 유형이라는 가정보다 얼마나 더 위험한지 알지 못합니다.
내가 정말로 모든 옵션을 다뤘는지 확신할 수 없으며 흥미로운 시나리오를 생각해 주시면 기쁠 것입니다.

그리고 이 문제가 있더라도 어쨌든 지금과 거의 동일합니다.

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

타이프스크립트가 이것으로부터 우리를 보호하도록 할 수 있지만 자바스크립트와 약간 다른 구문이 필요합니다.

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

그러면 다음과 같은 질문이 제기됩니다. typescript가 오류를 추론하게 하는 것보다 throws 키워드를 사용하면 언제 이점을 볼 수 있습니까?

@ConnorSinnott

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 절을 사용하는 경우와 컴파일러가 이를 추론하도록 하는 경우에 관해서는, 많은 경우에 필요하지 않은 많은 경우에 typescript에서 유형을 선언하는 것과 같다고 생각하지만 코드를 더 잘 문서화하기 위해 그렇게 할 수 있습니다. 나중에 읽는 사람이 더 잘 이해할 수 있습니다.

유형 안전성을 원하는 경우의 또 다른 접근 방식은 오류를 Golang의 값으로 처리하는 것입니다.
https://gist.github.com/brandonkal/06c4a9c630369979c6038fa363ec6c83
그래도 있으면 좋은 기능일 것입니다.

@brandonkal
그 접근 방식은 스레드의 앞부분에서 이미 논의되었습니다.
가능하지만 그렇게 하는 것은 자바스크립트가 허용하는 고유한 부분/기능을 무시하는 것입니다.

나는 또한 noexcept 지정자(#36075)가 현재로서는 가장 간단하고 최상의 솔루션이라고 생각합니다. 대부분의 프로그래머가 예외를 던지는 것은 반패턴을 고려하기 때문입니다.

다음은 몇 가지 멋진 기능입니다.

  1. 함수를 확인하면 오류가 발생하지 않습니다.
noexcept function foo(): void {
  throw new Error('Some exception'); // <- compile error!
}
  1. 중복 try..catch 블록에 대한 경고:
try { // <- warning!
  foo();
} catch (e) {}
  1. 결의는 항상 약속합니다:
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`
}

@moshest

대부분의 프로그래머는 예외를 던지면 안티 패턴을 고려합니다.

나는 그 "대부분의 프로그래머" 그룹에 속해 있지 않다고 생각합니다.
noexcept 는 훌륭하지만 이 문제의 핵심은 아닙니다.

도구가 있으면 사용하겠습니다. throw , try/catchreject 가 있으므로 사용하겠습니다.

타이프스크립트에 제대로 입력하는 것이 좋을 것입니다.

도구가 있으면 사용하겠습니다. throw , try/catchreject 가 있으므로 사용하겠습니다.

확신하는. 나도 그들을 사용할 것입니다. 제 요점은 noexcept 또는 throws never 가 이 문제의 좋은 출발점이 될 것이라는 것입니다.

멍청한 예: Math.sqrt(-2) 를 호출하면 오류가 발생하지 않는다는 것을 알고 싶습니다.

타사 라이브러리에도 동일하게 적용됩니다. 프로그래머로서 처리해야 하는 코드와 엣지 케이스에 대한 더 많은 제어를 제공합니다.

@moshest
하지만 왜?
이 제안에는 'noexpect' 제안의 모든 이점이 포함되어 있는데 왜 더 적은 비용으로 안주할까요?

오랜 시간이 걸리기 때문에(이번 호는 3년), 최소한 가장 기본적인 기능부터 먼저 시작할 수 있기를 바랐습니다.

@moshest
글쎄요, 저는 시간이 덜 걸리는 부분적 솔루션보다 시간이 더 오래 걸리는 더 나은 기능을 선호하지만 우리는 이 기능을 영원히 고수할 것입니다.

나중에 noexcept 를 쉽게 추가할 수 있을 것 같지만 사실 별도의 주제가 될 수 있습니다.

noexceptthrows never 와 동일합니다.

TBH $ try/catch 에 대한 유형 힌트를 제공하는 것보다 예외 처리(ala는 Go에서 error 를 사용하지 않음)를 보장하는 메커니즘을 갖는 것이 더 중요하다고 생각합니다.

@roll "보증"이 없어야합니다.
자바스크립트에서 예외를 처리해야 하는 것은 아니며 typescript에서도 반드시 그래야 하는 것은 아닙니다.

함수가 오류를 포착하거나 명시적으로 throws 선언하고 전달해야 하는 typescrypt의 엄격 모드의 일부로 옵션이 될 수 있습니다.

내 5센트를 추가하려면: null 를 반환하는 함수와 유사한 예외가 표시됩니다. 일반적으로 발생하지 않을 것으로 예상되는 일입니다. 그러나 그것이 발생하면 런타임 오류가 발생하는데 이는 좋지 않습니다. 이제 TS는 null 처리를 상기시키기 위해 "nullable이 아닌 유형"을 추가했습니다. throws 를 추가하면 예외도 처리하도록 상기시켜 이러한 노력을 한 단계 더 발전시키는 것으로 보입니다. 그렇기 때문에 이 기능은 꼭 필요하다고 생각합니다.

오류를 던지는 대신 오류를 반환하는 Go를 보면 두 개념이 그렇게 다르지 않다는 것을 더 명확하게 알 수 있습니다. 또한 일부 API를 더 깊이 이해하는 데 도움이 됩니다. 아마도 당신은 몇몇 함수가 던질 수 있다는 것을 모르고 프로덕션에서 훨씬 나중에 그것을 알아차릴 수 있습니다(예: JSON.parse , 얼마나 더 있는지 누가 알겠습니까?).

@obedm503 이것은 실제로 TS의 철학입니다. 기본적으로 컴파일러는 아무 것도 불평하지 않습니다. 특정 항목을 오류로 처리하는 옵션을 활성화하거나 모든 옵션을 한 번에 활성화하는 엄격 모드를 활성화할 수 있습니다. 따라서 이것은 주어져야 합니다.

이 제안된 기능이 마음에 듭니다.
예외 유형 지정 및 추론은 전체 프로그래밍 역사에서 가장 좋은 것 중 하나가 될 수 있습니다.
❤️

안녕하세요, 저는 현재 TS에 있는 해결 방법을 찾는 데 약간의 시간을 할애했습니다. 함수 범위 내에서 던져진 오류의 유형을 얻을 수 있는 방법이 없기 때문에(또는 현재 메소드의 유형을 얻을 수 없음) 메소드 자체 내에서 예상되는 오류를 명시적으로 설정할 수 있는 방법을 알아냈습니다. 나중에 해당 유형을 검색하고 적어도 메서드 내에서 throw될 수 있는 항목을 알 수 있습니다.

여기 내 POC가 있습니다

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

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

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

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

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

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

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

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

class CustomError extends Error {

}

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

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

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

    throw new CustomError('Really no luck')
}

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

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

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

    }

    if (typedError instanceof Error) {

    }

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

}

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

긍정적인 피드백을 주셔서 대단히 감사합니다! 반환된 전체 유형을 throwable 유형으로 래핑하지 않고도 기존 코드를 리팩토링하는 것이 더 쉬울 수 있다는 것을 알고 있습니다. 대신 반환된 유형에 해당 유형을 추가할 수 있습니다. 다음 코드를 사용하면 다음과 같이 throw 가능한 오류를 추가할 수 있습니다.

// 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 사용이 마음에 들지 않습니다. 더 나은 방법을 찾으면 알려 드리겠습니다.

여기에서 결과 팁을 테스트할 수 있습니다 . 106행에서 typedError 를 가져갑니다.

@Xample 그것은 우리가 지금 가지고 있는 도구로 훌륭한 솔루션입니다!
하지만 실제로는 다음과 같은 작업을 수행할 수 있기 때문에 충분하지 않다고 생각합니다.

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

그리고 b의 ​​유형은 올바른 숫자로 추론되지만 try 안에 래핑된 경우에만
유효하지 않아야 합니다.

@luisgurmendezMLabs 무슨 말인지 잘 모르겠습니다. 56행에서 @Xamplerepo 를 따라가면 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 throwable 유형은 오류 유형을 함수에 고정하고 나중에 해당 유형을 복구할 수 있는 방법으로만 함수의 반환 유형 내에 있습니다. 나는 진짜로 오류를 반환하지 않고 그냥 던졌습니다. 따라서 try 블록 내에 있거나 변경되지 않으면 아무 것도 변경되지 않습니다.

@ivawzh 물어봐 주셔서 감사합니다. 그냥 당신을 위해 한 것입니다

@luisgurmendezMLabs 아 네 요점을 이해합니다. typescript가 발견된 첫 번째 유형만 유추하는 것 같습니다. 예를 들어 다음과 같은 경우:

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
}

나는 또한 이것에 대한 내 생각을 떨어 뜨리고 싶었습니다.

Error/null을 반환하고 무언가를 던지고 싶어하는 다양한 사용 사례가 있고 많은 잠재력을 가질 수 있기 때문에 이것이 구현하기에 정말 좋은 기능이 될 것이라고 생각합니다. 예외를 던지는 것은 어쨌든 자바스크립트 언어의 일부인데, 왜 우리는 이것을 입력하고 추론할 수 있는 옵션을 주면 안 될까요?

예를 들어 오류가 발생할 수 있는 작업을 많이 수행하는 경우 매번 반환 유형을 확인하기 위해 if 문을 사용해야 하는 것이 불편할 것입니다. 이러한 작업 중 하나가 throw되면 추가 코드 없이 catch에서 처리됩니다.

이것은 같은 방식으로 오류를 처리하려고 할 때 특히 유용합니다 . 예를 들어 express/node.js에서 NextFunction (오류 처리기)에 오류를 전달하고 싶을 수 있습니다.

매번 if (result instanceof Error) { next(result); } 를 수행하는 대신 이러한 작업에 대한 모든 코드를 try/catch로 래핑할 수 있으며 예외가 발생했기 때문에 catch에서 항상 이것을 오류 처리기, 그래서 catch(error) { next(error); }

또한 아직 논의된 것을 본 적이 없습니다(하지만 놓쳤을 수도 있지만 이 스레드에는 꽤 많은 주석이 있습니다!). 그러나 이것이 구현된 경우 사용하지 않고 throw하는 함수를 갖는 것이 필수 (예: 컴파일 오류)가 되어야 합니까? 함수 선언에 throws 절이 있습니까? 이렇게 하면 좋을 것 같지만 (우리는 사람들에게 throw를 처리하도록 강요하지 않고 함수가 throw한다는 것을 알려줄 뿐입니다) 여기서 가장 큰 우려는 Typescript가 이러한 방식으로 업데이트되면 현재 존재하는 많은 코드를 깰 수 있습니다.

편집: 내가 생각한 또 다른 사용 사례 @throws 태그를 사용하여 JSDoc을 생성하는 데에도 도움이 될 수 있다는 것입니다.

나는 지금 커밋된 문제 에서 내가 말한 것을 여기에서 반복할 것입니다.


저는 typescript가 대부분의 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 를 던져야 합니다. 평가가... 알 수 없기 때문입니다.
  • e 함수는 d 의 오류를 포착하지만 catch 블록에 throw 가 있으므로 여기에서 유형을 'too bad...' 로 유추합니다. 포함 throw 'primitive value' Error 를 던질 수 없다고 말할 수 있습니다. (JS 블랙 매직을 놓친 경우 수정해 주세요...)
  • f 함수는 b 에서 a 에서 했던 것과 동일하게 c 에서 상속합니다.
  • g 함수는 $#$ a 'unlucky' $, c unknown $를 상속하므로 '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 태그가 지정되어 있습니다. 더 많은 피드백을 제공하려면 어떻게 해야 합니까? 이것은 내가 자바 언어를 부러워하는 유일한 기능입니다

API 호출이 200이 아닌 코드를 반환할 때 특정 오류가 발생하는 사용 사례가 있습니다.

interface HttpError extends Error {
  response: Response
}

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

catch 블록을 입력할 수 없으면 개발자는 두 가지 유형의 오류가 발생할 수 있으며 둘 다 처리해야 한다는 사실을 잊게 됩니다.

나는 항상 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')) { .....}
  }
}

이 기능이 여전히 "피드백이 충분하지 않은" 이유는 무엇입니까? indexedDB, localstorage와 같은 브라우저의 API를 호출할 때 매우 유용합니다. 복잡한 시나리오에서 많은 실패를 일으키지만 개발자는 프로그래밍에서 인식하지 못합니다.

헤겔 은 이 기능을 완벽하게 가지고 있는 것 같습니다.
https://hegel.js.org/docs#benefits ("입력된 오류" 섹션으로 스크롤)

TypeScript에도 비슷한 기능이 있었으면 좋겠습니다!

DL;DR;

  • 약속의 거부 기능을 입력해야 합니다.
  • try 블록 내에서 던져진 모든 유형은 catch 의 오류 인수에 통합을 사용하여 추론해야 합니다.
  • error.constructor는 any 뿐만 아니라 실제 유형을 사용하여 올바르게 입력해야 throw된 오류가 누락되지 않습니다.

좋습니다. 아마도 우리는 우리의 필요와 기대가 무엇인지 간단히 설명해야 할 것입니다.

오류는 일반적으로 js에서 3가지 방식으로 처리됩니다.

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 라고 하는 거부를 입력할 수 없습니다.

다음은 Promise 생성자의 서명입니다. 이유는 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;
    }
}

catch 블록 앞에 오류 "Error" 2줄이 발생했음에도 불구하고 TS는 error (value)를 Error (type)으로 입력할 수 없습니다.

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

typescript가 try 범위 내에서 발생할 수 있는 오류 유형을 예측할 수 있는 누락된 정보는 없습니다.
그러나 내부의 기본 함수가 소스 코드 내에 없는 오류를 일으킬 수 있다는 점을 고려해야 하지만 "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 등급