logo hsb.horse
← 스니펫 목록으로 돌아가기

Snippets

TypeScript에서 경량 Result 타입 구현

try-catch를 사용하지 않는 에러 핸들링 패턴. data와 error를 분리한 Result 타입으로 타입 안전하게 에러를 처리한다.

게시일: 수정일:

비동기 처리의 에러 핸들링은 try-catch로 작성하는 경우가 많지만, 예외가 발생하는 위치와 처리하는 위치가 떨어지기 쉽다. Result 타입 패턴을 사용하면 반환값으로 성공과 실패를 명시적으로 분리하여 다룰 수 있다.

전제: Error 타입으로의 변환

unknown 타입의 에러를 확실하게 Error 타입으로 변환하는 헬퍼.

export function toError(error: unknown): Error {
if (error instanceof Error) return error;
return new Error(error as never);
}

Result 타입 정의

interface Failure {
data: undefined;
error: Error;
}
interface Success<T> {
data: T;
error: undefined;
}
type Result<T> = Success<T> | Failure;

fetch 래퍼 구현 예

import { toError } from './toError.js'
export async function fetcher<T>(
url: RequestInfo,
init?: RequestInit
): Promise<Result<T>> {
const UNDEF = undefined;
// fetch가 실패한 경우 (네트워크 에러 등)
const response = await fetch(url, init).catch(toError);
if (response instanceof Error) {
return { data: UNDEF, error: response };
}
// HTTP 에러 응답
if (!response.ok) {
return {
data: UNDEF,
error: new Error(`HTTP ${response.status}: ${response.statusText}`)
};
}
// 성공
const data = await response.json();
return { data, error: UNDEF };
}

사용 예

const result = await fetcher<User>("/api/user/123");
if (result.error) {
console.error("가져오기 실패:", result.error.message);
return;
}
// result.error가 undefined인 경우, TypeScript는 result.data를 T로 추론
console.log(result.data.name);

장점과 주의사항

장점:

  • 성공과 실패가 반환값의 타입으로 명확해진다
  • try-catch 블록이 줄어 가독성이 향상된다
  • 에러 핸들링을 강제할 수 있다 (생략하면 타입 에러)

주의사항:

  • ECMAScript의 Safe Assignment Operator 프로포절이 표준화되면, 내장으로 유사한 기능을 사용할 수 있게 될 수도 있다
  • Go나 Rust의 언어 내장 Result 타입만큼 완비되어 있지 않다
  • 검증 에러 등 상세한 에러 핸들링은 별도 구현이 필요하다

구현은 대충이지만, 소규모 프로젝트에서 타입 안전하게 에러를 처리하고 싶을 때 유효하다.