typescript에서 Rust의 결과 유형 에뮬레이션

24145 단어 typescriptwebdev

처음에...
...각 항목에 잘못된 처리가 없는 API 호출이 있습니다.우리 중 한 명은 이렇게 보인다.
  /**
   * Register a new (local) account.
   *
   * @param email Email of the new user.
   * @param password Password of the new user.
   *
   * @returns Promise of boolean about the success of the registration request.
   */
  public async registerLocalAccount(
    email: string,
    password: string
  ): Promise<boolean> {
    return this._fetch('/auth/local/register', {
      method: 'POST',
      body: JSON.stringify({ email, password })
    }).then(res => res.ok);
  }
문제는 실패에 대한 정보가 없다는 것이다.우리는 단지 응답 상태 코드가 200인지 검사할 뿐이다.실패할 때 사용자는'아이고, 문제가 생겼으니 다시 시도해 보자'는 메시지를 보게 된다.우리는 당연히 더 잘할 수 있지 않습니까?맞아요!Result<T, E>형을 찾아보자.

결과는...
export type Result<T, E> = Ok<T, E> | Err<T, E>;

export class Ok<T, E> {
  public constructor(public readonly value: T) {}

  public isOk(): this is Ok<T, E> {
    return true;
  }

  public isErr(): this is Err<T, E> {
    return false;
  }
}

export class Err<T, E> {
  public constructor(public readonly error: E) {}

  public isOk(): this is Ok<T, E> {
    return false;
  }

  public isErr(): this is Err<T, E> {
    return true;
  }
}

/**
 * Construct a new Ok result value.
 */
export const ok = <T, E>(value: T): Ok<T, E> => new Ok(value);

/**
 * Construct a new Err result value.
 */
export const err = <T, E>(error: E): Err<T, E> => new Err(error);
이것은 이 유형의 매우 간단한 버전입니다.이 방면의 영감은 온라인에서 발견된 각종 실현에서 비롯된다. 그 중 하나는 neverthrow이다.Neverthrow는 map과 같은 실용적인 기능도 구현했습니다.
몇 가지 고려 사항
  • 우리는 Result<T, E>형의 활성 인터페이스를 가지고 있는데 이것은 Ok 또는 Err이다
  • OkErr 유형에는 isOkisErr 유형의 보호 장치가 있는데 어떤 값이
  • 인지 구분할 수 있다
  • 우리는 okerr의 구조 함수를 가지고 있으며, 새로운 Result 유형의
  • 을 구성하는 데 사용된다.
    현재 이 간단한 용법은 다음과 같다.
    function getCount(): Promise<Result<number, Error>> {
      return fetch('/index-count')
        .then(res => res.json())
        .then((body): Ok<number, Error> => ok(body['count']))
        .catch(() => err(new Error('Something when wrong while fetching count')));
    }
    
    const res: Result<number, Error> = await getCount();
    
    // To access the count, we'll first have to check if the calculation succeeded.
    if (res.isOk()) {
      // Now we can access the value.
      console.log('Count is:', res.value);
    }
    
    if (res.isErr()) {
     // Now we can access the error.
     console.error('Oh no, there was an error:', res.error)
    }
    
    멋있습니다. 이것은 당신을 기이하고 우아한 오류 처리를 시작하게 하기에 충분할 것입니다.하지만 잠깐만, 더 있어!Result형의 뼈 주위에는 아직 살이 조금 있다.

    네트워크 오류에 대한 추가 정보
    코드에서 모든 API 반환 유형에 대해 일반적인 NetworkError 유형을 정의했습니다.보아하니 이렇다.
    export type NetworkError<T> = {
      // Was this error expected or not?
      unexpected: boolean;
      // Possible value for the error.
      value?: T;
    };
    
    // Utility function to construct a new NetworkError.
    export const networkError = <T>(
      unexpected: boolean,
      value?: T
    ): NetworkError<T> => ({ unexpected, value });
    
    여기에서, 우리는 가능한 모든 (이미 알고 있는) 오류를 대상에 포장합니다. 대상은 하나의 필드를 가지고 있으며, 이 필드는 오류가 예상치 못했는지 알려 줍니다.예상치 못한 오류는 네트워크 오류나 내부 서버 오류일 수 있습니다. 예상되는 오류는 잘못된 전자메일이나 약한 비밀번호와 같은 인증 오류입니다.
    갑자기 API 요청 프로세서가 다음과 같이 보입니다.
    // Error codes used by the backend.
    export enum ApiErrorCode {
      PasswordTooWeak = 1,
      EmailInvalid = 2,
      EmailAlreadyInUse = 3
    }
    
    export type RegisterLocalAccountError = {
      email?: ApiErrorCode.EmailInvalid | ApiErrorCode.EmailAlreadyInUse;
      password?: ApiErrorCode.PasswordTooWeak;
    };
    
    export type RegisterLocalAccount = Result<
      boolean,
      NetworkError<RegisterLocalAccountError>
    >;
    
    public async registerLocalAccount(
      email: string,
      password: string
    ): Promise<RegisterLocalAccount> {
      return this._fetch('/auth/local/register', {
        method: 'POST',
        body: JSON.stringify({ email, password })
      })
        .then(
          async (res): Promise<RegisterLocalAccount> => {
            if (res.ok) {
              return ok(true);
            }
    
            if (res.status === 400) {
              const e = await res
                .json()
                .then(
                  (reason: RegisterLocalAccountError): RegisterLocalAccount =>
                    err(networkError(false, reason))
                )
                .catch((): RegisterLocalAccount => err(networkError(true)));
    
              return e;
            }
    
            return err(networkError(true));
          }
        )
        .catch(() => err(networkError(true)));
    }
    
    너는 더 많은 유형을 말할 수 있다.나를 믿어라. 이것은 이유가 있다. typescript에서, 너는 상세한 switch 문장을 만들 수 있다.예를 들어, 이메일 오류 메시지 유틸리티는 다음과 같습니다.
    const emailError = (
      error: NetworkError<RegisterLocalAccountError> | null
    ): string => {
      if (
        error === null ||
        error.value === undefined ||
        error.value.email === undefined
      ) {
        return '';
      }
    
      switch (error.value.email) {
        case ApiErrorCode.EmailAlreadyInUse:
          return 'Email is already in use';
        case ApiErrorCode.EmailInvalid:
          return 'Invalid email';
      }
    };
    
    현재 RegisterLocalAccountError을 다음과 같이 확장하면
    export type RegisterLocalAccountError = {
      email?: ApiErrorCode.EmailInvalid | ApiErrorCode.EmailAlreadyInUse | 'random error';
      password?: ApiErrorCode.PasswordTooWeak;
    };
    
    컴파일러는 우리에게 큰 소리로 외친다.
    Function lacks ending return statement and return type does not include 'undefined'.  TS2366
    
        91 | const emailError = (
        92 |   error: NetworkError<RegisterLocalAccountError> | null
      > 93 | ): string => {
           |    ^
        94 |   if (
        95 |     error === null ||
        96 |     error.value === undefined ||
    
    비록 오류 메시지가 이상적이지는 않지만, (우리가 어떤 상황을 처리하지 않았는지 설명할 수는 없지만, 그것은 여전히 실행할 때의 오류 (또는 전혀 오류가 없음) 보다 100배 낫다.
    어쨌든 API 프로세서 코드를 좀 더 자세히 살펴보겠습니다.then 프로세서에는 다음과 같은 세 개의 반환점이 있습니다.
  • 해피 경로, 응답 상태 코드가 200
  • 이면 Ok 반환
  • "예상"400 상태 코드를 획득한 첫 번째 오류 경로입니다.이 경로에서 우리는 JSON 주체를 얻어 오류를 검증해야 한다.따라서 JSON이 성공적으로 해결되면 예상되는 네트워크 오류를 반환합니다.그러나 JSON 구문 분석에 실패하면 예상치 못한 네트워크 오류가 반환됩니다.
  • 상태 코드가 200 또는 400이 아니면 예상치 못한 네트워크 오류를 반환합니다.
  • Result<T, E> 구조 함수를 사용하여 모든 오류 값을 err에 포장합니다.마지막 catch을 주의해야 하는데, 그 중에서 우리는 의외의 네트워크 오류를 되돌아왔다.마지막 포획은 매우 중요하다. 왜냐하면 우리는try/catch로 호출자 코드를 오염시키고 싶지 않기 때문이다.반대로 우리는 Result 유형에 의존하기를 희망한다.

    결론
    이 주제에 대한 취향(주로 정적 유형과 함수 유형에 대한 익숙함)에 따라 Result<T, E> 유형이 유용할 수 있습니다.우리는 typescript에서 가장 많은 내용을 추출하여 개발을 돕고 기능이 강한 소프트웨어를 시종일관 제공하려고 합니다.결과 유형은 API 가져오기 및 내보내기를 유형 시스템에 모델링하는 작업의 일부입니다.만약 조작이 적절하다면, 우리는 두려움 없이 백엔드를 변경할 수 있으며, 백엔드의 백엔드 유형을 업데이트하는 것만 주의할 수 있다.이 때 컴파일러는 당신과 함께 작업할 것입니다. 오류 처리의 위치를 확장해야 할 수도 있다는 것을 알려 줍니다.
    앞에서 제시한 결과 유형의 뚜렷한 단점은 그것이 하나의 종류이지 일반적인 대상이 아니라는 것이다.이 클래스들을 Redux 저장소의 일반 서열화 대상으로 변환하기를 원할 수도 있습니다.

    좋은 웹페이지 즐겨찾기