Result 타입은 오류를 값으로 다루기 위한 방법 중 하나로 성공 또는 실패를 표현하기 위한 패턴이다. 예를 들어 Result<Success, Failure>는 성공 시에는 Success 타입을, 실패 시에는 Failure 타입을 얻게 된다는 것을 의미한다.
Result는 단순히 값일 뿐이므로 예외처럼 제어 흐름을 무너뜨리지 않는다. 즉, Result 타입은 일반적인 코드와 동일한 제어 흐름을 따르는 예외 던지기의 대안으로 볼 수 있다.
다음 예시는 간단한 Result 타입의 모습을 보인다.
type Result<Success, Failure> =
| { successful: true; value: Success }
| { successful: false; error: Failure };
function success<T>(value: T): Result<T, never> {
return { successful: true, value };
}
function failure<E>(error: E): Result<never, E> {
return { successful: false, error };
}예외를 값으로 다루기
Result 타입을 사용하면 예외를 던지는 코드를 감싸서 값으로서 처리할 수 있다. 다음 attempt 함수는 fn 매개변수에 예외를 던질 수 있는 함수를 전달한다. fn 함수를 호출한 뒤 예외가 발생할 경우, 예외를 외부로 위임하지 않고 값으로서 반환한다.
type Result<Success, Failure> =
| { successful: true; value: Success }
| { successful: false; error: Failure };
function attempt<T>(fn: () => T): Result<T, unknown> {
try {
return { successful: true, value: fn() };
} catch (error) {
return { successful: false, error };
}
}attempt 함수는 예외가 자주 발생하는 Node.js의 내장 라이브러리와 함께 사용할 때 편리하다. node:fs 모듈의 statSync 메서드는 매개변수로 전달한 경로에 파일 또는 디렉터리가 없으면 예외를 던진다.
import { statSync } from "node:fs";
const stats = statSync("/example/path/unknown.txt"); // ENOENT 예외를 던진다.예외를 try ... catch 문으로 처리하지 않고 값으로서 다루기 위해 Result 타입을 적용한 attempt 함수를 사용할 수 있다. TypeScript는 successful 속성의 값을 확인한 뒤 해당 블록 안에서 value 또는 error가 존재함을 보장한다 — 판별된 유니온 —. 이를 통해 개발자는 런타임 에러에 대한 걱정 없이 안전하게 값에 접근할 수 있다.
import { statSync } from "node:fs";
type Result<Success, Failure> =
| { successful: true; value: Success }
| { successful: false; error: Failure };
function attempt<T>(fn: () => T): Result<T, unknown> {
try {
return { successful: true, value: fn() };
} catch (error) {
return { successful: false, error };
}
}
const result = attempt(() => statSync("/example/path/unknown.txt"));
if (result.successful) {
console.log(result.value);
} else {
console.error(result.error);
}