Quick Flow
async function main() {
const data = await Promise.resolve({ status: "ok" });
console.log(data);
}
void main().catch((err) => {
console.error("main failed:", err);
process.exitCode = 1;
});await한 실패는 가장 가까운 try/catch 또는 반환 Promise로 전파됩니다. 복구하지 않는 catch는 다시 throw하고, 프로그램의 진입점은 Promise를 .catch()로 닫아 종료 코드를 정합니다. unhandledRejection은 복구 위치가 아니라 누락된 전파 경로를 찾는 안전망입니다.
에러 처리
async 에러 흐름에서 먼저 보는 대표 형태는 아래입니다.
await를 포함한try/catch- Promise 체인의
.catch() - 엔트리포인트
main().catch(...) - 마지막 안전망
unhandledRejection
await 누락은 가장 흔한 누수 지점
async function fetchData() {
throw new Error("network failed");
}
async function handler() {
try {
fetchData(); // await 없음
} catch (err) {
// 여기로 안 온다
}
}이 코드는 try/catch가 있어 보여도 실제로는 Promise를 기다리지 않습니다. 그래서 에러는 호출자 범위를 벗어나 unhandledRejection 쪽으로 흘러갈 수 있습니다.
catch 처리
.catch()도 에러를 끝까지 보내지 않을 수 있다
fetchUser(id)
.catch((err) => {
console.error(err);
})
.then((user) => {
console.log(user);
});여기서 catch가 값을 다시 던지지 않으면 체인은 fulfilled처럼 이어질 수 있습니다. 그래서 "로깅만 하고 끝"인 catch는 종종 더 위험합니다.
즉 "에러를 기록했다"와 "에러를 처리했다"는 다릅니다. 복구가 아니면 다시 throw하거나 실패 값을 명시적으로 반환해야 흐름이 분명해집니다.
엔트리포인트
최상위에서는 실패를 명시적으로 닫는다
async function main() {
await doWork();
}
void main().catch((err) => {
console.error("main failed:", err);
process.exitCode = 1;
});Node에서는 이 패턴이 중요합니다.
- entrypoint Promise는 반드시 닫는다
- 처리되지 않은 rejection은 마지막 안전망으로 로깅하고 원인을 추적한다
process.exit(1)은 아직 기록 중인 로그나 I/O를 끊을 수 있습니다. 이미 더 진행할 작업이 없다면 process.exitCode = 1로 종료 상태만 지정하고 이벤트 루프가 자연스럽게 비워지게 두는 편이 보통 더 안전합니다.
unhandledRejection은 복구 지점보다 추적 지점에 가깝다
이 이벤트에서 뭔가를 수습해 서비스 정상 흐름으로 되돌리기보다, 왜 rejection이 여기까지 새었는지 추적하는 편이 더 중요합니다. 핵심은 안전망 자체보다 rejection이 여기까지 새지 않게 에러 경로를 닫는 데 있습니다.
현재 Node는 --unhandled-rejections 기본값이 throw이므로, 처리되지 않은 rejection이 uncaught exception 경로로 이어질 수 있습니다. 그래서 "경고만 찍히고 끝난다"는 예전 감각으로 읽지 않는 편이 안전합니다. 다만 실제 동작은 플래그 설정에도 영향을 받으므로, 운영 환경에서 어떤 모드로 실행하는지도 함께 확인해야 합니다.
어디서 끊어야 하나
체크포인트
- async 호출이면
await여부 먼저 본다 - catch가 다시 throw하는지 본다
- 최상위 Promise는
.catch()로 닫는다 unhandledRejection은 누락 추적용 안전망- 로깅만 하고 끝나는 catch인지도 같이 본다
주의할 점
unhandled rejection을 "나중에 로그만 찍고 넘어가는 경고"처럼 다루면 장애가 지연된 형태로 남습니다. Node 앱에서는 이 이벤트가 보였다는 사실 자체가 에러 경로 설계가 비어 있다는 뜻에 더 가깝습니다.
참고 링크
2 sources