JavaScript Error.cause: 체인 방식으로 디버깅 품질을 극적으로 높이는 방법 ("Error chaining in JavaScript: cleaner debugging with Error.cause")

okorion·2025년 12월 3일

1. 핵심 요약

  • 기존 JS 에러 처리의 문제는 원인(원본 에러) 추적이 끊긴다는 점
  • ES2022의 Error.cause는 “에러 체인”을 표준 방식으로 제공
  • 상위 에러 메시지를 명확하게 유지하며 원본 stack/type을 그대로 보존
  • 테스트·로깅·서비스 계층 분리 구조에서 큰 효과
  • DevTools는 cause를 자동 표시하지 않으므로 직접 로깅 필요
  • TS 사용 시 target: es2022, lib: ["es2022"] 필수

2. 기존 에러 처리 방식의 문제점 (Why)

문제: 원인 에러를 감싸면 스택이 손실됨

try {
  JSON.parse('{ bad json }');
} catch (err) {
  throw new Error('Something went wrong: ' + err.message);
}

문제점:

  • 원본 스택(trace) 유실
  • 원본 에러 타입 유실 (SyntaxError인지 알 수 없음)
  • 상위·하위 에러 맥락이 분리됨

→ 실제 서비스 계층 구성(서비스 → 래퍼 → 인프라)에서 디버깅 난이도 증가


3. Error.cause: 표준 에러 체인 (What)

try {
  try {
    JSON.parse('{ bad json }');
  } catch (err) {
    throw new Error('Something went wrong', { cause: err });
  }
} catch (err) {
  console.error(err.stack);
  console.error('Caused by:', err.cause.stack);
}

결과:

Error: Something went wrong
   ...
Caused by: SyntaxError: Unexpected token b...

장점

  • 상위 레이어 메시지: “무슨 일이 일어났는가?”
  • cause: “왜 문제가 발생했는가?”
  • 원본 stack / type / message 유지
  • ES 표준 방식이므로 서드파티 없이 안정적

4. 실전 예시: 서비스 계층에서 에러 체인 적용

function fetchUserData() {
  try {
    JSON.parse('{ broken: true }');
  } catch (parseError) {
    throw new Error('Failed to fetch user data', { cause: parseError });
  }
}

try {
  fetchUserData();
} catch (err) {
  console.error(err.message); // 상위 메시지
  console.error(err.cause);   // SyntaxError
}

특징

  • cause는 non-enumerable
    → 로그/for...in에 자동 노출되지 않음
  • 필요할 때만 명시적으로 접근 가능

5. 기존(ES2022 이전) 방식의 문제

1) 문자열 합치기

throw new Error("x failed: " + err);

→ 타입 유실 / 스택 유실

2) 커스텀 속성 부착

err.original = originalErr;

→ 통일되지 않은 관례 / 라이브러리별 혼란

3) 완전 래핑

→ 여전히 타입·스택 추적이 수동적

Error.cause는 이런 패턴을 대체하는 공식 해법.


6. 커스텀 에러에서도 사용 가능

class DatabaseError extends Error {
  constructor(message, { cause } = {}) {
    super(message, { cause });
    this.name = 'DatabaseError';
  }
}

TypeScript 설정

{
  "compilerOptions": {
    "target": "es2022",
    "lib": ["es2022"]
  }
}

안 하면 { cause }에 타입 에러 발생.


7. 테스트에서의 활용

expect(err.cause).toBeInstanceOf(ValidationError);
  • 상위 에러만 체크하던 기존 방식보다 명확한 원인 테스트 가능
  • 서비스 계층의 “실패 사슬(failure chain)”이 테스트 시 더 투명해짐

8. 로깅 시의 주의점 (How)

DevTools는 cause를 자동 출력하지 않음.

console.error(err);
console.error("Caused by:", err.cause);

전체 에러 체인을 재귀 출력

function logErrorChain(err, level = 0) {
  if (!err) return;
  console.error(' '.repeat(level * 2) + `${err.name}: ${err.message}`);

  if (err.cause instanceof Error) {
    logErrorChain(err.cause, level + 1);
  } else if (err.cause) {
    console.error(' '.repeat((level + 1) * 2) + String(err.cause));
  }
}

스택 전체를 출력하려면

function logFullErrorChain(err) {
  let current = err;
  while (current) {
    console.error(current.stack);
    current = current.cause instanceof Error ? current.cause : null;
  }
}

→ 마이크로서비스·대규모 계층 구조에서 필수


9. 다층 에러 체인 실전 예시

3단계 에러 흐름

  1. ConnectionTimeoutError
  2. DatabaseError (cause: #1)
  3. ServiceUnavailableError (cause: #2)
class ConnectionTimeoutError extends Error {}
class DatabaseError extends Error {}
class ServiceUnavailableError extends Error {}

try {
  try {
    try {
      throw new ConnectionTimeoutError('DB connection timed out');
    } catch (networkErr) {
      throw new DatabaseError('Failed to connect', { cause: networkErr });
    }
  } catch (dbErr) {
    throw new ServiceUnavailableError('Unable to save user data', { cause: dbErr });
  }
} catch (finalErr) {
  logErrorChain(finalErr);
}

출력

ServiceUnavailableError: Unable to save user data
  DatabaseError: Failed to connect
    ConnectionTimeoutError: DB connection timed out

→ “문제가 어디서 시작되었는지”를 명확하게 보여주는 계층적 오류 기록.


10. 지원 환경

  • Chrome 93+
  • Firefox 91+
  • Safari 15+
  • Edge 93+
  • Node.js 16.9+
  • Deno / Bun
    ⚠️ Babel/TS 트랜스파일러는 cause를 폴리필하지 않음

11. 요약: Modern Error Chaining

기능지원
new Error(message, { cause })✔️
커스텀 클래스에서 cause 전달✔️
원본 에러 스택/타입 보존✔️
디버깅·로깅·테스트 강화✔️
TypeScript ES2022 설정 필요✔️
체인 자동 출력 없음 → 직접 로깅⚠️

→ “원인 에러 손실” 문제를 표준적으로 해결하는 모던 패턴.


원문 - Error chaining in JavaScript: cleaner debugging with Error.cause

profile
Tech Blog

0개의 댓글