SHIN
TypeScript 실전 팁 10선
10편TypeScript 4.0부터 catch 변수의 기본 타입이 any 대신 unknown으로 변경되었습니다(useUnknownInCatchVariables).
// ❌ any를 그냥 사용 — 타입 안전하지 않음
try {
await fetchData()
} catch (err: any) {
console.log(err.message) // 런타임에 undefined일 수 있음
}
// ✅ unknown을 instanceof로 처리
try {
await fetchData()
} catch (err) {
if (err instanceof Error) {
console.log(err.message) // ✅ Error로 좁혀짐
} else if (typeof err === 'string') {
console.log(err)
} else {
console.log('알 수 없는 오류:', err)
}
}class AppError extends Error {
constructor(
message: string,
public readonly code: string,
public readonly statusCode = 500
) {
super(message)
this.name = this.constructor.name
}
}
class ValidationError extends AppError {
constructor(message: string, public readonly field: string) {
super(message, 'VALIDATION_ERROR', 400)
}
}
class NotFoundError extends AppError {
constructor(resource: string) {
super(`${resource}을(를) 찾을 수 없습니다`, 'NOT_FOUND', 404)
}
}
// 에러 처리
function handleError(err: unknown): Response {
if (err instanceof ValidationError) {
return Response.json({ field: err.field, message: err.message }, { status: 400 })
}
if (err instanceof NotFoundError) {
return Response.json({ message: err.message }, { status: 404 })
}
if (err instanceof AppError) {
return Response.json({ message: err.message }, { status: err.statusCode })
}
return Response.json({ message: '서버 오류' }, { status: 500 })
}함수가 에러를 던지는 대신 반환값으로 에러를 표현합니다.
type Result<T, E = Error> =
| { ok: true; value: T }
| { ok: false; error: E }
function ok<T>(value: T): Result<T> {
return { ok: true, value }
}
function err<E = Error>(error: E): Result<never, E> {
return { ok: false, error }
}
// 사용
async function parseJson(text: string): Promise<Result<unknown>> {
try {
return ok(JSON.parse(text))
} catch (e) {
return err(e instanceof Error ? e : new Error(String(e)))
}
}
const result = await parseJson('{ invalid }')
if (result.ok) {
console.log(result.value) // ✅ 성공
} else {
console.error(result.error.message) // ✅ 에러
}function getErrorMessage(err: unknown): string {
if (err instanceof Error) return err.message
if (typeof err === 'string') return err
if (typeof err === 'object' && err !== null && 'message' in err) {
return String((err as { message: unknown }).message)
}
return '알 수 없는 오류가 발생했습니다'
}
// 활용
try {
await riskyOperation()
} catch (err) {
toast.error(getErrorMessage(err)) // 항상 string 반환
}// Promise를 Result로 래핑
async function tryCatch<T>(
promise: Promise<T>
): Promise<Result<T>> {
try {
return ok(await promise)
} catch (e) {
return err(e instanceof Error ? e : new Error(String(e)))
}
}
// 사용 — try/catch 없이 깔끔하게
const { ok: success, value, error } = await tryCatch(fetchUser(1))
if (!success) {
console.error(error.message)
return
}
// 여기서 value는 User
console.log(value.name)에러 처리를 타입 시스템에 녹이면 "이 함수가 어떤 에러를 낼 수 있는가"가 시그니처에서 명확하게 드러나 유지보수성이 높아집니다.