슈뢰딩거의 기능

Promises and throw considered harmful



예를 들어 아래 코드를 보자

function createUser(email: string): User {
    if (users.includes(email)) {
        throw new Error(`User with email ${email} already exists`)
    }

    ...

    return newUser;
}


또는 비동기 버전

function createUser(email: string): Promise<User> {
    return new Promise((resolve, reject) => {
        if (users.includes(email)) {
            throw new Error(`User with email ${email} already exists`)
        }

        ...

        return newUser;
    })
}



try {
    const result = createUser('email');
    ...
} catch (error /* error is not typed */) {
    ...
}


이러한 함수를 호출하는 사람이 함수를 호출하고 런타임에 결과를 확인하지 않고는 함수 호출로 인해 오류가 발생할 수 있고 예상되는 오류 유형을 알 수 있는 방법이 없습니다. 또는 특히 타사 라이브러리를 다룰 때 소스 코드를 검사합니다.

그렇기 때문에 슈뢰딩거의 함수와 같은 함수를 호출합니다. 함수를 호출하거나 검사하기 전에는 함수가 오류인지 아닌지 알 수 없습니다.

I try to avoid directly returning promises for async operations (most especially async operations that may fail), because promises can reject without any indication what the error will be and are hard to type the error value.



그래서 제가 따르는 원칙은 약속을 절대 거부하거나 오류를 던지지 않는 것입니다. 누군가는 "그러나 그것들은 타당한 이유로 JavaScript에 추가되었습니다"라고 말할 수 있습니다. 예, 그러나 그들은 또한 코드를 예측할 수 없게 만들고 검사를 입력하기 어렵게 만듭니다. Typescript 사용자의 경우 유형 캐스팅 및 if 문을 사용하여 오류 유형을 확인해야 합니다.

해결책



내가 취하는 접근법은 Result 또는 Err 결과의 두 가지 유형 중 하나일 수 있는 Ok 를 반환하는 것입니다. 기본적으로 약속은 Err 또는 Ok 값으로 해결됩니다. 예를 들어

type Err<T> = { _tag: "Err"; value: T };
type Ok<T> = { _tag: "Ok"; value: T };

type Result<E, A> = Err<E> | Ok<A>;


우리는 Result 유형을 ErrOk 의 합집합으로 선언했습니다. 예를 들어 다음과 같이 작성할 수 있습니다.

try {
    let file = await readFile(...);
    return {_tag: "Ok", value: file}
} catch (err) {
    return {_tag: "Err", value: err}
}


이제 비동기 작업에 대해 완전히 형식화된 반환 값이 있습니다. 이 함수를 호출하면 다음을 얻을 수 있습니다.

let result = ... // {_tag: "Err", value: ...} | {_tag: "Ok", value: ...}

if (result._tag === "Err") {
    // value is of whatever type we defined as error
}


이제 이전에 본 함수를 다시 작성해 보겠습니다.

function createUser(email: string): Result<Error, User> {
    if (users.includes(email)) {
        return {_tag: "Err", value: new Error(`User with email ${email} already exists`)}
    }

    ...

    return {_tag: "Ok", value: newUser};
}

...


그리고 결과 유형을 구별하는 데 도움이 되는 일부 유틸리티 기능

function isErr<E, A>(result: Result<E, A>): result is Err<E> {
  return result._tag === "Err";
}

function isOk<E, A>(result: Result<E, A>): result is Ok<A> {
  return result._tag === "Ok";
}


마지막으로 함수를 호출합니다.

const result = createUser("email");

if (isErr(result)) {
  console.log(result.value); // Error()
}

// our result is `Ok` here
console.log(result.value); // User


이것은 함수형 프로그래밍에서 Either 또는 Maybe라고 불리는 것에 대한 빠른 실행입니다. 이와 같은 다른 아이디어에 대해 자세히 알아보려면 fp-ts을 확인하십시오.

좋은 웹페이지 즐겨찾기