본문으로 건너뛰기

throw로 오류 알리기

자바스크립트 목차오류와 모듈레슨 56 / 76

목차

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:13

7번 줄의 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)로 넘어가는지 따라가 보세요.

8단계 중 1단계. `function pay` 선언은 실행 전에 전역 칸의 `pay` 이름표로 올라갑니다. 7번 줄부터 실행합니다.

  • 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:13

Node의 at 줄은 가장 최근에 멈춘 안쪽 함수부터 맨 위에 적힙니다.

  1. at price (main.js:3:11): 실제 throw가 실행되어 멈춘 자리(price 함수 안 3번 줄)입니다.
  2. at total (main.js:8:10): 그 price를 부른 자리(total 함수 안 8번 줄)입니다.
  3. 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
홀수 불가
2
  • x가 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
초과

끝까지 내려오면 저절로 표시됩니다. 이 기기에만 저장합니다.

자바스크립트 목차로