📝

9. 예외처리

 

9. 예외처리란?

9-1. 오류의 종류

프로그래밍을 하다보면 크고 작은 오류가 발생한다. MDN 문서에서 수 없이 많은 에러의 종류를 확인 할 수 있는데, 자바스크립트는 프로그램이 실행 되기 전에 발생하는 ‘구문 오류'와 프로그램 실행 도중 발생하는 ‘예외' 두 가지 상황으로 인해 오류가 발생한다. 이 두 가지 상황에 대해 먼저 알아보자.
 
  • 구문오류(Syntax Error)란? 프로그램이 실행하기 전에 발생하여 코드가 실행조차 되지 않는 문법적 오류를 말한다. 이 오류는 일반적으로 코드에 세미콜론이 누락되거나 짝이 맞지 않는 대괄호 또는 중괄호가 있는 등 자바스크립트가 요구하는 구문의 규칙이 충족되지 않을 때 발생한다. 구문 오류가 발생하게 되면 브라우저는 코드를 분석조차 하지 못하고 종료된다.
 
function error(){ console.log('이 코드는 syntaxError가 던져질 거에요!' }
 
이렇게 코드를 실행하면 콘솔에 ‘Syntax Error’가 찍힌다. 구문오류는 오류의 위치와 오류의 내용을 친절히 알려주기 때문에 비교적 쉽게 해결할 수 있다.
notion imagenotion image
 
  • 런타임오류(runtime error) 또는 예외(exception)란? 코드의 실행 중간에 발생하는 오류로 ‘Syntax Error’ 이외의 모든 오류를 예외로 분류한다 (예: Reference Error, RangeError, TypeError, URIError 등). 프로그램을 실행할 때 발생하기 때문에 코드 실행 이전에는 오류를 발견하기 어렵다.
    • 아래와 같이 존재하지 않는 메소드를 호출하면 ‘TypeError’ 오류가 발생한다.
       
window.printf();
notion imagenotion image

9-2. 논리적 오류와 예외처리의 필요성

위 두 종류의 오류는 콘솔에 자동으로 검출되어 출력된다. 때문에 해당하는 오류 문구를 검색하여 수정하면 되므로 처리가 비교적 용이하다. 반면에 ‘논리적 오류(Logical Error)’는 그 원인을 파악하기 어려운 에러다. 코드 작성자의 논리적 실수가 있어 예상한 값이 출력되지 않는 경우인데, 프로그램은 정상적으로 동작하고 종료된다.
 
function add(a,b) { console.log(a * b) }; add(3,5);
 
위의 코드가 + 연산을 사용했어야 하는 코드라고 생각해보자. 의도와는 전혀 다른 결과물을 출력하고도 원인을 찾지 못할 가능성이 크다. 간단한 연산이라 에러의 충격이 크진 않지만, 상품의 가격을 합산하는 코드라고 생각하면 에러의 무게가 상당히 느껴질 것이다.
자바스크립트는 다른 프로그래밍 언어에 비해 유연하기 때문에 예외 발생이 적은 편이다. 이런 유연함은 ‘논리적 오류'가 발생한 시점에도 계속해서 프로그램이 실행되는 커다란 문제를 야기할 수 있다. 따라서 개발자는 다양한 예외 상황이 발생할 수 있다는 것을 전제하고 이에 대응하는 코드를 미리 작성하는 것이 중요하다. 오류를 처리하는 것은 더 나은 사용자 경험을 제공하고 흐름을 제어할 수 있는 프로그램 관리가 가능하기 때문에 오류 처리 전략을 잘 세워야 한다.

9-3. 기본 예외 처리와 에러의 전파

간단한 코드의 경우에는 조건문의 사용을 통해 문제 발생의 여지가 있는 부분을 미리 예측하고 처리해 주어야 한다.
 
function error(a, b) { if (b <= 0) { throw "0 보다 큰 수를 입력하세요!"; } return a / b; } console.log(error(12, 4)); // 3 console.log(error(3, 0)); // 0 보다 큰 수를 입력하세요
 
코드가 복잡해지면 크롬 개발자 도구의 디버깅 툴을 사용하자. 디버깅 툴을 사용하면 스크립트 내의 코드 단위별로 에러 추적이 가능하다. 디버깅 툴의 자세한 사용 방법은 아래 주소를 참고하라.
💡
Chrome으로 디버깅 하기: https://ko.javascript.info/debugging-chrome
 
기본적으로 에러는 콜 스택의 아래 방향(호출자 방향)으로 전파된다.
 
알잘딱깔센 JavaScript알잘딱깔센 JavaScript
알잘딱깔센 JavaScript
 
앞에서 살펴 보았듯이 setTimeout이나 Promise의 콜백 함수는 테스크큐나 마이크로테스크큐에 일시 저장되었다가 콜 스택이 비면 그 때 이벤트 루프에 의해서 콜 스택으로 푸시된 후 실행된다. 이때 콜 스택에 푸시된 콜백 함수의 실행 컨텍스트는 콜 스택의 가장 아래에 존재하기 때문에, 에러를 전파할 호출자가 존재하지 않는다. 때문에 setTimeoutPromise의 콜백 함수는 반드시 함수 내부에 try...catch 문을 통해 예외처리를 해주어야 한다. 본격적으로 고급 예외처리 방법에서 알아보자.

2. 고급 예외처리 방법

2-1. try-catch-finally

코드를 실행하다보면 에러는 언제나 일어날 수 있다. 이는 굉장히 흔한 일이며 서버 문제, 예상하지 못한 사용자의 입력 등 여러 방면으로 에러가 날 상황은 무궁무진하다. 흔한 일이라고 하지만 만약 이로 인해 서버가 다운되는 등 작동이 중지가 된다면 엄청난 손해가 일어날 것이다. 우리는 예외 처리를 하여 예측 불가한 최악의 상황에 대비해야 한다.
예외 처리를 하는 여러 방법이 있는데, try-catch-finally에 대해 알아보도록 하자. try-catch-finally 문은 에러가 날 상황에 대비하는 코드로 프로그램이 강제 종료되는 상황을 막고자하는 목표가 있다.

2-1-1. 기본 구조

try-catch-finally의 기본 구조는 다음과 같다.
try{ //실행할 코드 } catch(error){ /* try 코드 블록에서 error가 발생하면 실행되는 코드 try 구문에서 생성된 Error객체가 catch 매개변수로 전달 됨 */ } finally { /* 에러 발생과 무관하게 반드시 한 번 실행되는 코드 try 블록이나 catch 블록이 실행을 마친 뒤, 마지막에 실행 됨*/ }
 
try 코드 블록에는 실행할 코드가 들어간다. 코드가 실행되다가 에러가 발견되면 에러에 대한 내용이 담긴 Error객체를 생성한다. Error 객체catch의 매개변수(error)에 전달되고 catch 코드 블록 안의 코드가 실행된다. catch구문은 생략 가능하지만, 예외를 처리하는 구문이기 때문에 생략은 거의 하지 않는다. 마지막으로 finally 코드 블록은 에러 상황에 상관없이 마지막에 실행되는 코드를 담고 있다. finally 구문은 생략 가능하다 .
 

2-1-2. 동작 과정

  • 에러 발생의 경우
      1. try 코드 실행
      1. try 실행 중 에러 발견, try 코드 실행 중단
      1. try 구문 안에서 에러에 대한 정보가 담긴 Error객체를 생성하고 catch에게 전달
      1. catch 코드 실행, catch 구문이 제어 흐름을 가짐
      1. finally 코드 실행
       
  • 에러가 발생하지 않은 경우
      1. try 코드 전부 실행
      1. finally 코드 실행
       

2-1-3. 예제

간단한 예제를 통해 위의 동작 과정을 살펴보자.
  • 에러가 발생한 경우
    •  
      try { alert('try 블록 시작!'); const 과자 = '새우깡'; 과자 = '고구마깡'; //const 변수는 값을 변경할 수 없다. 에러 발생. alert('try 블록 코드 전부 실행'); } catch(err) { alert(err); //에러에 대한 정보가 담긴 Error 객체를 인자값을 받아서 alert함. } finally{ alert('finally'); } //try 블록 시작! //TypeError: Assignment to constant variable. //finally
 
  • 에러가 발생하지 않은 경우
    •  
      try { alert('try 블록 시작!'); const 과자 = '새우깡'; alert('try 블록 코드 전부 실행'); } catch(err) { alert(err); }finally{ alert('finally'); } //try 블록 시작! //try 블록 코드 전부 실행 //finally
 
Promise에서도 .catch구문으로 에러 핸들링을 적용할 수 있다. url을 잘못 기입했을 경우, 어떻게 예외 처리를 할 수 있을까?
 
fetch('잘못된 url') .then(response => response.json()) .catch(err => alert(err)); //TypeError: Failed to execute 'fetch' on 'Window': Failed to parse URL from 잘못된 url //Promise객체
 
.then 메서드 다음에 .catch 구문을 작성하여 에러를 핸들링 할 수 있다. Promise에서도 .catch구문으로 에러 핸들링을 적용할 수 있다. API URL이 업데이트 되지 않아서 서버와 연결이 안된다면 어떻게 에러 처리를 할 수 있을까?

2-1-4. 유의 사항

1. try-catch 코드 블럭은 동기적으로 실행된다.
동기적으로 동작하기 때문에 setTimeout과 같이 동작하는 시간이 정해진 코드에선 작동하지 않는다.
 
  • try 구문→ finally구문→ 정해진 시간이 흐른 뒤, setTimeout 실행
 
try-catch-finally을 모두 읽은 뒤에, setTimeout함수가 작동하기 때문에 이후에 catch 블록은 작동하지 않는다. 에러 핸들링을 하는 catch를 거치지 못하고 콘솔창에 에러 메시지가 뜬다.
try { alert("try 시작"); setTimeout(() => { const 과자 = "새우깡"; 과자 = "고구마깡"; alert("try 블록 코드 전부 실행"); }, 2000); } catch (error) { alert(error); } finally { alert("finally"); } //try 시작 //finally // Uncaught TypeError: Assignment to constant variable.
 
  1. try-catch-finally 구문은 실행 가능한 코드에서 동작한다.
    1. 코드 식 자체가 성립이 안된다면 JS엔진이 코드 자체를 읽는 것이 불가하다. 따라서 에러가 발생하고 try-catch 함수를 읽지 못하게 된다.
       
      try { console.log('try'; //SyntaxError } catch(error) { alert(error); } //Uncaught SyntaxError: missing ) after argument list
      catch 코드 블록을 읽지 못하고 구문 오류가 난 곳에서 바로 에러 메시지를 보낸다
       

2-2. 예외 객체와 throw구문

예외 객체는 try-catch 구문에서 catch 괄호 안에 입력하는 식별자이다. e, exception 등의 변수명으로 표현하며 예외 정보를 담고 있다.
예외 객체의 기본 속성은 name, message 이다.
 
try { const snack = '과자'; snack = '음료수'; } catch(exception) { console.log(`예외 이름 : ${exception.name}`); console.log(`에외 메시지 : ${exception.message}`); }
 
notion imagenotion image
 
const로 선언한 snack 변수에 다른 값을 넣는 경우 TypeError가 발생하고 그에 따른 설명이 메시지로 나오는 것을 볼 수 있다.
throw 구문은 사용자가 정의한 예외를 강제로 발생시킬 때 사용한다. 예외가 발생하면 현재 실행되고 있던 동작이 중지되고 throw 호출자 함수 사이에 catch 블록이 실행되며 없다면 종료된다. throw로 예외를 사용할 때는 throw 다음에 String, Number, Boolean typeObject 도 가능하다.
 
function getSnack(snack) { if (snack !== 0) return 10/snack; else throw 'snack 없음!'; } try { let snack = 0; getSnack(snack); } catch (e) { console.log(e); console.log('0으로는 나눌 수 없다!'); } // snack 없음! // 0으로는 나눌 수 없다!
자바스크립트는 0으로 나눌 경우 Infinity가 값으로 나온다. 이럴 경우를 대비해 try-catch 구문에서 getSnack 함수를 호출할 때 throw 구문으로 예외를 정의해놓는다. catch 블록이 존재하기 때문에 종료되지 않고 catch 블록이 실행된다. 아래 MDN 문서를 참고하면 좋다.
 
그 외에도 프로미스 사용시 예외가 발생하는 경우는 앞서 8. 비동기-프로미스 페이지를 참고하면 된다.