비동기 처리의 에러 핸들링은 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 타입만큼 완비되어 있지 않다
- 검증 에러 등 상세한 에러 핸들링은 별도 구현이 필요하다
구현은 대충이지만, 소규모 프로젝트에서 타입 안전하게 에러를 처리하고 싶을 때 유효하다.
hsb.horse