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
이다Ok
과 Err
유형에는 isOk
과 isErr
유형의 보호 장치가 있는데 어떤 값이 ok
과 err
의 구조 함수를 가지고 있으며, 새로운 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
프로세서에는 다음과 같은 세 개의 반환점이 있습니다.Ok
반환Result<T, E>
구조 함수를 사용하여 모든 오류 값을 err
에 포장합니다.마지막 catch
을 주의해야 하는데, 그 중에서 우리는 의외의 네트워크 오류를 되돌아왔다.마지막 포획은 매우 중요하다. 왜냐하면 우리는try/catch로 호출자 코드를 오염시키고 싶지 않기 때문이다.반대로 우리는 Result
유형에 의존하기를 희망한다.결론
이 주제에 대한 취향(주로 정적 유형과 함수 유형에 대한 익숙함)에 따라
Result<T, E>
유형이 유용할 수 있습니다.우리는 typescript에서 가장 많은 내용을 추출하여 개발을 돕고 기능이 강한 소프트웨어를 시종일관 제공하려고 합니다.결과 유형은 API 가져오기 및 내보내기를 유형 시스템에 모델링하는 작업의 일부입니다.만약 조작이 적절하다면, 우리는 두려움 없이 백엔드를 변경할 수 있으며, 백엔드의 백엔드 유형을 업데이트하는 것만 주의할 수 있다.이 때 컴파일러는 당신과 함께 작업할 것입니다. 오류 처리의 위치를 확장해야 할 수도 있다는 것을 알려 줍니다.앞에서 제시한 결과 유형의 뚜렷한 단점은 그것이 하나의 종류이지 일반적인 대상이 아니라는 것이다.이 클래스들을 Redux 저장소의 일반 서열화 대상으로 변환하기를 원할 수도 있습니다.
Reference
이 문제에 관하여(typescript에서 Rust의 결과 유형 에뮬레이션), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/duunitori/mimicing-rust-s-result-type-in-typescript-3pn1텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)