
안녕하세요!
개발하다 보면 에러 때문에 머리 싸맬 때가 많으시죠?
특히 에러의 원인을 찾는 건 정말 어려운 숙제인데요.
오늘은 JavaScript (자바스크립트) 디버깅을 훨씬 수월하게 만들어주는 숨겨진 보석 같은 기능, 바로 error.cause
에 대해 알아보려고 합니다.
- 디버깅의 어려움
디버깅하면서 가장 힘든 점이 뭘까요?
저는 단연 에러의 근본 원인을 추적하는 거라고 생각하는데요.
이런 상황을 한번 상상해보세요.
const func = () => {
doSth('A');
doSth('B');
};
func
함수가 에러를 던졌을 때, 이 에러가 어느 단계에서 발생했는지 바로 알 수 있을까요?
doSth('A')
때문인지, doSth('B')
때문인지, 아니면 func
함수 자체의 문제인지… 에러 메시지만 봐서는 도대체 감이 안 잡힐 때가 많습니다.
에러에 맥락 정보가 부족하기 때문인데요.
- 일반적인 해결 방법
이 문제를 해결하려고 흔히 사용하는 방법이 있습니다.
바로 try...catch
구문을 사용해서 에러를 잡고, 새로운 에러를 던지는 건데요.
const func = () => {
try {
doSth('A');
} catch (error) {
throw new Error('A에서 에러 발생', error);
}
try {
doSth('B');
} catch (error) {
throw new Error('B에서 에러 발생', error);
}
};
이렇게 하면 에러가 어디에서 발생했는지 좀 더 쉽게 알 수 있습니다.
하지만 이 방법은 몇 가지 아쉬운 점이 있는데요.
- 에러 세부 정보 손실: 에러에 자세한 정보 (예: payload (페이로드), HTTP (에이치티티피) 상태 코드, 에러 코드)가 담겨 있는 경우, 이 방법은 새로 만든 에러에
doSth
의 에러 메시지만 추가할 뿐입니다. 원래 에러의 중요한 세부 정보들, 심지어 stack trace (스택 트레이스)까지 싹 다 날아가 버립니다. - 로그 가독성 저하: 에러가 발생할 수 있는 지점이 두 군데보다 많아지면, 로그가 너무 복잡해져서 읽기가 힘들어집니다.
- 의도 표현의 모호함: 새로운 에러가 특정
doSth
함수에서 잡은 에러 때문에 발생했다는 것을 명확하게 코드로 표현하기 어렵습니다. 코드만 봐서는 의도가 제대로 전달되지 않을 수 있다는 거죠.
- error.cause, 문제 해결사 등장
이런 문제들을 해결하기 위해 ECMAScript 2022 (이에스6 2022)에서 드디어 error.cause
라는 멋진 기능이 나왔습니다.
error.cause
를 사용하면 새로운 error object (에러 객체)를 만들 때 에러의 근본 원인을 지정할 수 있습니다.
error.cause
를 사용해서 에러들을 연결하면, 마치 에러 체인처럼 만들 수 있어서, 디버깅할 때 문제의 근본 원인을 더 쉽게 추적할 수 있습니다.
간단한 예시를 한번 볼까요?
try {
// 에러가 발생할 수 있는 작업
} catch (error) {
throw new Error('뭔가 잘못됐어요', { cause: error });
}
이렇게 하면 에러 사이에 인과 관계를 만들 수 있습니다.
앞서 봤던 func
예시를 error.cause
를 사용해서 다시 한번 바꿔볼까요?
const func = () => {
try {
doSth('A');
} catch (error) {
throw new Error('doSth("A") 실행 중 에러 발생', { cause: error });
}
try {
doSth('B');
} catch (error) {
throw new Error('doSth("B") 실행 중 에러 발생', { cause: error });
}
};
이렇게 하면 하위 레벨 함수 (doSth('A')
)에서 던져진 에러를 잡아서, 맥락 정보를 담은 새로운 에러 메시지 ("doSth('A') 실행 중 에러 발생")를 던지면서도, 원래 에러의 자세한 정보 ("A는 유효하지 않은 인수입니다.")는 그대로 유지할 수 있습니다.
- 에러 체인 만들기
error.cause
의 또 다른 장점은 에러 체인을 만들 수 있다는 건데요.
에러 체인을 사용하면 애플리케이션의 여러 계층을 거쳐서 문제의 원인을 더 깊이 파고들 수 있습니다.
const func = () => {
try {
try {
try {
doSth('A');
} catch (error) {
throw new Error('깊이 3에서 에러 발생', { cause: error });
}
} catch (error) {
throw new Error('깊이 2에서 에러 발생', { cause: error });
}
} catch (error) {
throw new Error('깊이 1에서 에러 발생', { cause: error });
}
};
console.log(error.cause.cause); // Error at depth 3 (깊이 3에서 에러 발생)
Node.js (노드js)에서는 cause
속성이 있는 에러를 console (콘솔)에 특별하게 처리해주는데요.
관련된 모든 에러 스택을 한꺼번에 출력해줍니다.
const cause = new Error('HTTP (에이치티티피) 서버가 500 상태 코드로 응답했습니다.');
const symptom = new Error('메시지 전송 실패', { cause });
console.log(symptom);
// 출력 결과:
// Error: 메시지 전송 실패
// at REPL2:1:17
// at Script.runInThisContext (node:vm:130:12)
// ... 7 lines matching cause stack trace ... (cause 스택 트레이스와 일치하는 7줄)
// at [_line] [as _line] (node:internal/readline/interface:886:18) {
// [cause]: Error: HTTP (에이치티티피) 서버가 500 상태 코드로 응답했습니다.
// at REPL1:1:15
// at Script.runInThisContext (node:vm:130:12)
// at REPLServer.defaultEval (node:repl:574:29)
// at bound (node:domain:426:15)
// at REPLServer.runBound [as eval] (node:domain:437:12)
// at REPLServer.onLine (node:repl:902:10)
// at REPLServer.emit (node:events:549:35)
// at REPLServer.emit (node:domain:482:12)
// at [_onLine] [as _onLine] (node:internal/readline/interface:425:12)
// at [_line] [as _line] (node:internal/readline/interface:886:18)
- 결론
에러 맥락과 세부 정보를 바로 확인할 수 있다면 디버깅이 훨씬 쉬워지겠죠?
error.cause
기능을 사용해서 "catch (잡기) + rethrow with context (맥락을 담아 다시 던지기)" 패턴을 적용하면, 디버깅 효율을 크게 높일 수 있습니다.
try {
doSth();
} catch (error) {
throw new Error('doSth 관련 맥락 정보', { cause: error });
}
error.cause
를 활용하면 에러 추적도 쉬워지고, 코드 가독성과 유지보수성도 높일 수 있습니다.
JavaScript (자바스크립트) 디버깅, 이제 error.cause
로 더 스마트하게 해보시는 건 어떠세요?
'Javascript' 카테고리의 다른 글
AbortController, 아직 제대로 모르세요? - 숨겨진 기능부터 활용 꿀팁까지! (0) | 2025.03.22 |
---|---|
Node.js Cluster (클러스터) 완벽 해부: 핵심 개념 파헤쳐보기 - Node.js (노드js) 성능 향상의 비밀, 알아볼까요? (0) | 2025.03.22 |
Node.js Event Loop 속속들이 파헤쳐보기: 싱글 스레드 모델의 비밀, 알아볼까요? (0) | 2025.03.22 |
TypeScript 컴파일러, Go 언어로 갈아탄다고요?! 속사정 한번 알아볼까요? (0) | 2025.03.22 |
TypeScript 객체 타입 Union과 Intersection (0) | 2025.03.22 |