기다리는 일도 실패할 수 있습니다. 네트워크가 끊기거나 서버가 응답하지 않으면 Promise는 이행(fulfilled) 대신 거부(rejected) 상태가 됩니다. 이번 레슨에서는 async 함수 안에서 난 오류나 거부된 Promise를 try / catch로 안전하게 잡는 법과, 왜 await를 빼먹으면 try로 감싸도 오류가 잡히지 않는지 알아봅니다.
await와 try / catch로 오류 잡기
async 함수 안에서 throw new Error(...)를 실행하면, 그 함수가 돌려주는 Promise는 거부(rejected) 상태가 됩니다. 이 거부된 Promise 앞에 await를 붙이면 그 자리에서 다시 일반 오류처럼 던져지므로, 10단원에서 배운 try / catch로 그대로 잡을 수 있습니다.
async function load() {
throw new Error('연결 끊김');
}
try {
await load();
console.log('성공');
} catch (e) {
console.log(e.message);
}
연결 끊김5번 줄의 await load()에서 거부된 Promise가 오류로 바뀌어 6번 줄을 건너뛰고 catch (e)로 넘어갑니다.
await를 빼먹으면 try가 잡지 못한다
만약 try 블록 안에서 await를 빼고 load()만 부르면 어떻게 될까요?
async function load() {
throw new Error('연결 끊김');
}
try {
load();
} catch (e) {
console.log(e.message);
}
console.log('끝');
끝main.js:2 throw new Error('연결 끊김'); ^ Error: 연결 끊김 at load (main.js:2:9) at main.js:5:3- 5번 줄의
load()는 거부된Promise상자만 돌려주고 멈추지 않습니다. await로 상자를 열어 보지 않았으므로try블록은 아무 오류 없이 지나가고, 9번 줄의'끝'이 먼저 찍힙니다.- 지금 도는 코드가 다 끝난 뒤, 아무도 처리하지 않은 거부(
Unhandled Rejection)가 남아 Node가 오류를 찍고 멈춥니다.
따라서 try / catch로 비동기 함수의 오류를 잡으려면 반드시 호출 앞에 await를 붙여야 합니다.
then 사슬에서는 .catch()로 잡기
await 대신 .then() 사슬을 쓸 때는 끝에 .catch((err) => { ... })를 이어 붙여 거부된 오류를 받습니다.
async function load() {
throw new Error('시간 초과');
}
load()
.then((v) => {
console.log('값:', v);
})
.catch((e) => {
console.log(e.message);
});
시간 초과앞의 load()나 .then() 어디에서 오류가 나든 중간의 .then()을 건너뛰고 .catch()가 받아 처리합니다.
자주 틀리는 곳
1. await 없이 try로 감싸면 잡힌다고 생각함
가장 흔한 실수입니다. async 함수는 오류를 밖으로 바로 던지지 않고 거부된 Promise 상자에 담아 돌려줍니다. 상자를 열어 오류를 꺼내는 일은 await가 하므로, try 안에서는 늘 await를 함께 써야 합니다.
2. 잡지 않은 거부가 그 줄에서 바로 멈춘다고 생각함
await 없이 거부된 Promise를 방치하면 그 줄에서 바로 멈추지 않고, 지금 도는 동기 코드(console.log('끝'))가 끝까지 다 찍힌 뒤에야 프로그램이 멈춥니다. (한편 웹 브라우저에서는 잡지 않은 거부가 나도 콘솔에 빨간 줄만 찍힐 뿐 페이지 자체는 멈추지 않습니다.)
요약
async함수 안에서throw new Error(...)가 실행되면 거부(rejected) 상태의Promise를 돌려준다.try와await:try안에서await fn()으로 기다려야 거부된Promise가 오류로 꺼내져catch에서 잡힌다.await를 빼먹으면try블록을 그대로 지나쳐 버려, 지금 도는 코드가 끝난 뒤 미처리 거부 오류로 멈춘다..then()사슬을 쓸 때는 끝에.catch((err) => ...)를 붙여 오류를 잡는다.
연습 문제
문제 1
다음 코드를 실행했을 때 출력되는 세 줄을 순서대로 쓰세요.
async function item(id) {
if (id <= 0) {
throw new Error('번호 오류');
}
return `상품 ${id}`;
}
for (const n of [1, 0, 2]) {
try {
console.log(await item(n));
} catch (e) {
console.log(e.message);
}
}
힌트
n = 0일 때 item(0)이 거부된 Promise를 돌려주고, await가 그 오류를 꺼내 catch (e)로 넘깁니다.
정답
상품 1
번호 오류
상품 2n = 1일 때는'상품 1'이 찍힙니다.n = 0일 때는await item(0)에서 오류가 던져져번호 오류가 찍힙니다.- 오류가 잡혔으므로 다음 바퀴
n = 2도 돌아'상품 2'가 찍힙니다.
문제 2
다음 코드는 fail()이 던진 '서버 점검' 오류를 catch에서 잡으려 했지만, await를 빼먹어 잡지 못하고 멈췄습니다. 5번 줄 한 줄만 고쳐 서버 점검이 찍히게 만드세요.
async function fail() {
throw new Error('서버 점검');
}
try {
fail();
} catch (e) {
console.log(e.message);
}
힌트
거부된 Promise가 try 안에서 오류로 풀리도록 fail() 앞에 await를 붙이세요.
정답
- 고친 줄:
await fail();
async function fail() {
throw new Error('서버 점검');
}
try {
await fail();
} catch (e) {
console.log(e.message);
}서버 점검문제 3
재고 qty가 0이면 '품절' 오류를 던지고 아니면 '주문 완료'를 돌려주는 async 함수 order(qty)가 있습니다. try / catch와 await를 써서 order(0)을 부르고, 오류가 나면 e.message를 출력하는 코드를 완성하세요.
품절
async function order(qty) {
if (qty === 0) {
throw new Error('품절');
}
return '주문 완료';
}
// 여기에 try / catch와 await로
// order(0)을 부르는 코드를 쓰세요
힌트
try 안에서 const res = await order(0);을 실행하고, catch (e)에서 console.log(e.message);를 실행합니다.
정답
async function order(qty) {
if (qty === 0) {
throw new Error('품절');
}
return '주문 완료';
}
try {
const r = await order(0);
console.log(r);
} catch (e) {
console.log(e.message);
}품절