JavaScript 공부 - 에러 처리

그래도 아무튼 개발자·2023년 4월 27일
0

JavaScript

목록 보기
10/11
post-thumbnail

JavaScript에서 에러 처리를 구현하는 방법

  • 예외적인 상황이 발생하면 반환하는 값(null이나 -1)을 if문이나 단축평가, 옵셔널 체이닝 연산자를 통해 처리
  • 에러 처리 코드를 미리 등록해 두고 에러 발생 시 에러 처리 코드로 점프시키기
  • 일반적으로 JavaScript의 에러 처리는 두 번째 방법을 의미한다.

    에러 처리를 하는 이유

    대부분의 개발자들에게 에러가 발생하지 않는 코드를 작성하는 것은 불가능에 가깝다. 즉 에러는 언제든지 발생할 수 있다. 만일 에러 발생 부분을 방치하게 되면 프로그램은 실행되지 않고 강제로 종료된다.

    try...catch

    가장 많이 쓰이는 에러 처리 방법이다.

    console.log('start');
    
    foo();		//선언된적 없는 foo 호출로 인해 ReferenceError 발생
    
    console.log('end');		//이 코드는 실행되지 않음.

    위 에러를 try...catch 문법을 이용해서 코드를 수정할 수 있다.

    console.log('start');
    
    try {
    	foo();
    }
    catch (error) {
    	console.log('에러발생', error);		//에러발생 ReferenceError: foo is not defined
    }
    console.log('end');		//정상 실행

    우선적으로 try 안에 있는 코드가 실행된다. 근데 만일 try 안에서 에러가 발생한다면 발생한 에러는 catch 문의 error 변수에 전달이 되고, 이후 catch 코드가 실행되는 구조이다.

    try...catch...finally

    finally까지 추가할 수 있다. finally 코드는 에러 발생 여부와 상관없이 반드시 실행되는 코드이다.

    console.log('start');
    
    try{
    	foo();
    }
    catch(err) {
    	console.error(err);		//에러는 뜨지만 프로그램이 종료되진 않는다.
    }
    finally {
    	console.log('finally');		//정상출력
    }
    console.log('end');		//정상출력

    Error 객체

    Error 생성자 함수는 다양한 종류의 에러 객체를 생성한다. 각각의 에러는 에러를 상세히 설명하는 에러 message를 인수로 전달할 수 있다.
    Error 생성자 함수가 만든 에러 객체는 인수에 전달하는 message 프로퍼티와 추가로 stack 프로퍼티도 갖는다. 여기서 stack 프로퍼티 값은 에러를 발생시킨 콜스택의 호출 정보를 나타내는 문자열이고, 이는 디버깅의 목적으로 사용된다.

    Error 생성자 함수 종류

    1 @ 2;		//SyntaxError
    foo();		//ReferenceError
    null.foo();		//TypeError
    new Array(-1);		//RangeError
    decodeURIComponent('%');		//URIError

    throw문

    그런데 여기서 중요한 점은 위에서 Error 생성자 함수로 에러 객체를 생성한다고 표현되어 있다. 에러 객체를 '생성'한다고 했지 에러가 '발생'했다고 하진 않았다. 즉 객체만 생성되었을 뿐 발생은 또 다른 개념이다.

    try {
    	new Error('wrong');		//객체만 생성되었을 뿐 에러가 발생하진 않는다.
    }
    catch (error) {
    	console.error(error);
    }

    에러 특유의 빨간 배경이 등장하지 않는다.

    이러면 에러 객체를 생성한 의미가 없어진다. 여기서 사용되는 것이 throw이다.
    에러를 발생시키기 위해 try 코드에 throw문을 이용하여 에러 객체를 던져야한다. 에러를 던지면 catch 코드에서 에러 변수가 생성되고, 던져진 에러 객체가 할당된다. 이후 catch 코드 블록도 실행된다.

    try {
    	throw new Error('wrong');
    }
    catch (error) {
    	console.error(error);
    }

    에러 특유의 빨간 배경이 등장한다. 에러가 잘 발생된 것을 알 수 있다.

    에러의 전파

    에러는 기본적으로 호출자의 방향으로 전파가 된다. 즉 콜스택의 아랫방향으로 전파된다는 의미이다.

    const foo = () => {
    	throw Error('foo에서 발생'); // (4)
    }
    
    const bar = () => {
    	foo(); // (3)
    }
    
    const baz = () => {
    	bar(); // (2)
    }
    
    try {
    	baz(); // (1)
    }
    catch (error) {
    	console.error(error);
    }

    위 예제를 살펴보면 (1)에서 baz 함수를 호출한다. 그러면 (2)로 가서 bar()함수가 호출되고, 이후 (3)에서 foo()함수가 호출되고, foo 함수는 (4)에서 에러를 던진다. 여기서 던진 에러는 아래 그림과 같이 호출자에게 전파되면서 전역에서 캐치된다.

    이처럼 던진 에러를 캐치하지 않으면 호출자 방향으로 전파되기 때문에, 적절하게 캐치해서 대응해야 프로그램을 강제로 종료시키지 않고 코드의 실행 흐름을 복구할 수 있다. 만일 던져진 에러를 어디에서도 캐치하지 않는다면 프로그램은 강제종료가 된다.

    추가 : 비동기 함수나 프로미스 후속 처리 메서드의 콜백함수에서 던진 에러는 처리하기 힘든 이유

    비동기 함수인 setTimeout이나 프로미스 후속 처리 메서드의 콜백함수의 경우 호출자가 없다.
    setTimeout이나 프로미스 후속 처리 메서드의 콜백함수는 위 예시처럼 바로 콜스택에 들어가는 것이 아니라 태스크 큐나 마이크로태스크 큐에서 일시 저장되었다가 콜스택이 비면 이벤트 루프에 의해 콜스택으로 푸쉬되어 실행되는데, 이때 콜스택에 푸쉬된 콜백함수의 실행 컨텍스트는 스택의 가장 하부에 위치하게 된다. 다시 말하면 예외가 발생하는 시점과 try-catch 문이 감싸고 있는 시점이 일치하지 않는다는 것이다. 따라서 막상 에러를 전파해야 하는 호출자가 존재하지 않는 꼴이 되어버리기 때문에 에러 처리가 힘들게 된다.

    0개의 댓글