try와 catch에서는 JSON.parse처럼 자바스크립트가 내는 오류를 잡았습니다. 그런데 주문 수량에 -2나 0이 들어오면 자바스크립트는 아무 오류 없이 음수 금액을 계산해 버립니다. 이럴 때 잘못된 값을 발견한 자리에서 직접 오류를 만들어 알리는 문장이 throw new Error('...')입니다.
먼저 수량이 0 이하일 때 throw 없이 그대로 계산하면 어떤 일이 벌어지는지 봅니다.
function pay(qty) {
return qty * 4500;
}
console.log(pay(-2));
-9000-9000원이 계산되어도 아무 오류가 나지 않으니 잘못된 주문이 그대로 넘어갑니다.
throw new Error로 함수 멈추기
함수 안에서 throw new Error('설명')을 실행하면, 그 자리에서 함수를 즉시 멈추고 만든 오류 객체를 함수를 부른 쪽으로 던집니다. 부른 쪽에서 try / catch로 잡지 않으면 프로그램이 오류 메시지를 찍고 멈춥니다.
function pay(qty) {
if (qty <= 0) {
throw new Error('수량 오류');
}
return qty * 4500;
}
console.log(pay(2));
console.log(pay(0));
console.log(pay(1));
9000main.js:3 throw new Error('수량 오류'); ^ Error: 수량 오류 at pay (main.js:3:11) at main.js:8:137번 줄의 pay(2)는 if를 지나 5번 줄에서 9000을 돌려줍니다. 반면 8번 줄의 pay(0)은 3번 줄에서 throw new Error('수량 오류')를 만나 5번 줄의 return을 건너뛰고 멈추며, 9번 줄도 실행되지 않습니다.
부른 쪽에서 try / catch로 감싸면 멈추는 대신 던져진 오류 객체를 catch (err)가 받습니다.
function pay(qty) {
if (qty <= 0) {
throw new Error('수량 오류');
}
return qty * 4500;
}
try {
console.log(pay(0));
} catch (err) {
console.log(err.message);
}
수량 오류pay(0) 안에서 던진 Error 객체가 어떻게 pay 호출 칸을 빠져나와 부른 쪽의 catch (err)로 넘어가는지 따라가 보세요.
- 3번 줄에서
throw new Error('수량 오류')가 실행되는 순간,pay안의 남은 5번 줄은 건너뛰고pay호출 칸이 돌려주는 값 없이 사라집니다. - 8번 줄의
console.log도 실행되지 않고, 가장 가까운catch (err)가 오류 객체를 받아err.message인'수량 오류'를 찍습니다.
at 줄을 위에서 아래로 읽기
함수가 다른 함수를 부르고 그 안쪽에서 오류가 던져지면, 오류 메시지 아래의 at 줄에 함수들이 불린 경로가 차례로 적힙니다.
function price(menu) {
if (menu !== '라테') {
throw new Error('없는 메뉴');
}
return 4500;
}
function total(menu, qty) {
return price(menu) * qty;
}
console.log(total('홍차', 2));
main.js:3 throw new Error('없는 메뉴'); ^ Error: 없는 메뉴 at price (main.js:3:11) at total (main.js:8:10) at main.js:10:13Node의 at 줄은 가장 최근에 멈춘 안쪽 함수부터 맨 위에 적힙니다.
at price (main.js:3:11): 실제throw가 실행되어 멈춘 자리(price함수 안 3번 줄)입니다.at total (main.js:8:10): 그price를 부른 자리(total함수 안 8번 줄)입니다.at main.js:10:13: 처음total('홍차', 2)를 부른 바깥 10번 줄입니다.
파이썬과 비교: 파이썬의
Traceback은 처음 부른 바깥 줄이 위에, 오류가 난 가장 안쪽 줄이 맨 아래에 적히지만, 자바스크립트의at줄은 반대로 맨 위 첫 번째at줄이 오류가 난 자리이고 아래로 내려갈수록 부른 쪽으로 거슬러 올라갑니다.
자주 틀리는 곳
1. throw 뒤 줄도 실행된다고 생각함
throw는 return과 마찬가지로 실행되는 즉시 함수를 빠져나갑니다. throw 아래에 적은 줄은 절대 실행되지 않습니다.
function check(n) {
if (n < 0) {
throw new Error('음수');
console.log('여기는 안 옴');
}
return n;
}
try {
check(-1);
} catch (e) {
console.log(e.message);
}
음수2. new Error(...) 대신 문자열을 그대로 던짐
자바스크립트 문법상 throw '품절'처럼 문자열을 던질 수도 있지만, 그렇게 던지면 오류 객체가 아니라서 err.name, err.message, 그리고 어디서 났는지 알려 주는 at 줄 정보가 모두 없습니다.
try {
throw '품절';
} catch (e) {
console.log(e.name);
console.log(e.message);
}
undefined
undefined오류를 던질 때는 늘 throw new Error('품절')처럼 new Error(...)로 만들어 던지세요.
3. 안쪽 함수마다 습관적으로 try를 감싸 오류를 삼킴
값을 검사하는 안쪽 함수(pay) 안에서 스스로 try / catch로 감싸 버리면, 바깥에서는 오류가 났는지도 모른 채 undefined를 받아 계산을 이어 가게 됩니다. 안쪽 함수는 throw new Error(...)로 알리기만 하고, 어떻게 처리할지는 함수를 부르는 바깥쪽에서 try / catch로 결정합니다.
요약
throw new Error('설명'): 그 자리에서 함수를 즉시 멈추고Error객체를 부른 쪽으로 던진다.- 부른 쪽에서
try / catch로 잡으면err.message로'설명'을 읽을 수 있고, 끝까지 잡지 않으면 프로그램이 멈춘다. at줄 읽는 차례: 첫 번째at줄이 오류가 난 가장 안쪽 함수이고, 아래로 내려갈수록 그 함수를 부른 바깥 자리이다.- 문자열을 그대로 던지지 않고 늘
new Error(...)를 만들어 던진다.
연습 문제
문제 1
다음 코드를 실행했을 때 출력되는 세 줄을 순서대로 쓰세요.
function half(n) {
if (n % 2 !== 0) {
throw new Error('홀수 불가');
}
return n / 2;
}
for (const x of [8, 5, 4]) {
try {
console.log(half(x));
} catch (err) {
console.log(err.message);
}
}
힌트
8과 4는 짝수라 n / 2를 돌려주고, 5는 홀수라 throw new Error('홀수 불가')가 실행되어 catch로 넘어갑니다.
정답
4
홀수 불가
2x가8일 때half(8)은4를 돌려줍니다.x가5일 때half(5)가 3번 줄에서 오류를 던져 5번 줄을 건너뛰고,catch에서err.message인홀수 불가를 찍습니다.- 오류가 잡혔으므로 다음 바퀴인
x = 4로 이어져half(4)가2를 찍습니다.
문제 2
다음 코드는 재고가 부족할 때 오류를 던져 catch에서 err.message인 '품절'을 찍으려 했지만 undefined가 찍혔습니다. 3번 줄 한 줄만 고쳐 '품절'이 찍히게 만드세요.
function take(left, qty) {
if (qty > left) {
throw '품절';
}
return left - qty;
}
try {
console.log(take(2, 5));
} catch (err) {
console.log(err.message);
}
힌트
문자열 '품절'에는 .message 속성이 없습니다. new Error(...)로 오류 객체를 만들어 던지세요.
정답
- 고친 줄:
throw new Error('품절');
function take(left, qty) {
if (qty > left) {
throw new Error('품절');
}
return left - qty;
}
try {
console.log(take(2, 5));
} catch (err) {
console.log(err.message);
}품절new Error('품절')로 던지면err.message에'품절'이 담깁니다.
문제 3
쿠폰 할인액 c가 가격 p보다 크면 '초과' 오류를 던지고, 아니면 p - c를 돌려주는 함수 cut(p, c)를 작성하세요.
300
초과
// 여기에 cut 함수를 쓰세요
for (const c of [500, 900]) {
try {
console.log(cut(800, c));
} catch (e) {
console.log(e.message);
}
}
힌트
if (c > p)일 때 throw new Error('초과');를 실행하고, 그 아래에 return p - c;를 씁니다.
정답
function cut(p, c) {
if (c > p) {
throw new Error('초과');
}
return p - c;
}
const list = [500, 900];
for (const c of list) {
try {
console.log(cut(800, c));
} catch (e) {
console.log(e.message);
}
}300
초과