자바스크립트

Section 11. 자바스크립트 에러 핸들링

포칼이 2023. 4. 7. 14:49

에러 핸들링 error handling 의 필요성

에러 발생에 대비하지 않으면 프로그램이 종료된다. 

console.log('에러 발생 전');

// ⚠️ 오류를 발생시키는 코드
(3).split('');

// 출력되지 않음
console.log('에러 발생 후');

 

I. 자바스크립트의 에러 핸들링

1. try ...catch 문

console.log('에러 발생 전');

try {
  (3).split('');

} catch (e) {
  console.error('🛑 에러!!', e);
}

console.log('에러 발생 후');

try ...catch문을 썼더니 맨 위에 있는 예시에서는 프로그램이 멈춰서 마지막 출력문이 출력 되지 않았지만 이번에는 출력이 되었다. 

 

정리하자면

try 블록

  • 에러 발생 여지가 있는 코드를 포함한다.
  • 이곳에서 발생한 에러는 프로그램을 멈추지 않는다.

catch 블록

  • 에러 발생시 실행할 코드를 포함한다.
  • 발생한 오류 객체를 인자로 받는다. 
const arr = ['ABC', '가나다', 123, '123'];

function getLetterArray (str) {
  // 💡 인자로 어떤 타입의 값이 주어질지 모르는 상황
  try {
    return str.split('');

  } catch (e) {
    console.error('🛑 에러!!', e);
    return [];
  }
}

arr.forEach(i => {
  console.log(getLetterArray(i));
});

인자로 어떤 값을 받을지 모르는 상황에서 try블록 에서는 일단 작업을 한다. 그리고 아무 에러도 없으면 함수를 종료한다. 

try블록에서 에러가 발생하게 되면 catch 블록으로 넘어가서 작업을 하고 반환값으로는 빈 배열을 반환하고 함수를 종료하게 된다. 

 

2. try ... catch ... finally 문

finally 블록

오류가 발생 여부와 관계없이 한 번 실행되는 코드를 포함한다.

try나 catch문에 return이 있더라도 반드시 실행된다.

 

function connect () { console.log('☀️', '통신 연결'); }
function disconnect () { console.log('🌙', '통신 연결 해제'); }
function sendArray (arr) { console.log('전송', arr); }

function sendStringAsArray (str) {
  connect();

  try {
    sendArray(str.split(''));
    return true;

  } catch (e) {
    console.error('🛑 에러!!', e);
    return false;

  } finally {
    // 💡 전송 성공 여부와 관계없이 연결은 끊어야 함
    disconnect();
    console.log('- - - - - - - -');
  }

  // ❓ 이곳에 넣는 것과 무엇이 다른가?
  // 아래로 대체하여 실행해 볼 것
  // disconnect();
  // console.log('- - - - - - - -');
}

['ABC', '가나다', 123, '123'].forEach(i => {
  console.log(
    sendStringAsArray(i) 
    ? '[성공]' : '[실패]', '\n\n'
  );
});

오류발생 여부와 관게없이 finally 블록에 있는 작업은 실행이 됐다.  

그렇다면 finally 블록에서의 작업을 밖으로 빼서 실행하면 어떨까?

함수는 return문을 만나면 종료된다. 그리고 disconnect가 실행이 되지 않아 출력이 안되는 것을 볼 수 있다. 실무에서 connect 등을 자원을 썼으면 반드시 종료를 해 주어야 하는데 이처럼 finally에서 disconnect를 하지 않으면 자원이 부족해져서 서버의 기능이 마비될 수 있으니 조심해야 한다.

 

II. Error 객체

  • 에러 발생 시 던져지는 thrown 객체이다.
  • 에러에 대한 정보를 담고 있다.
  • 에러가 발생하지 않아도, 직접 생성하여 던지기가 가능하다.

1. 기본 생성과 사용법

const error = new Error('철권 8 발매일');

console.error(error);

두 번째 인자로 이유를 명시할 수도 있다.

const error = new Error(
  '철권 8 발매일',
  { cause: '보이지가 않음' }
);

console.error(error);

이것을 확인해보면 다음과 같다

console.log(error.name); //Error
console.log(error.message); //철권 8 발매일

// cause를 입력했을 경우
console.log(error.cause); //보이지가 않음

이처럼 에러 객체의 인스턴스 프로퍼티와 메서드를 통해서 내용을 확인 할 수도 있다.

 

의도적으로 에러 발생시키는 법

throw new Error('이유를 묻지 마라');

 

2. 에러의 여러 종류

아래의 에러들은 모두 Error부터 상속받는다.

어떤 문제에 의한 에러인지 쉽게 식별 가능하도록 하는 것들이다.

 

오류에 종류에 따라 대처하는 예시

const errorFuncs = [
  // 자료형에 맞지 않는 메서드 호출
  () => { (3).split(''); },

  // 선언되지 않은 함수 호출
  () => { hello(); },

  // 부적절한 숫자를 인자로 전달
  () => { (123.45).toFixed(-1); }
];
errorFuncs.forEach(func => {
  try {
    func();

  } catch (e) {
    if (e instanceof TypeError) {
      console.error('자료형 확인하세요.');
      return;
    }
    if (e instanceof ReferenceError) {
      console.error('선언 안 된 거 쓴 거 없는지 확인하세요.');
      return;
    }
    console.error('아니, 뭘 한 거에요?');
  }
});

여러 종류의 에러를 발생시키는 함수들을 각각 만들고 catch 문을 활용하여 오류의 종류에 따라 대처하게 만들었다.

 

직접 오류를 생성하여 던지기

// 특정 월의 당번으로 지원하는 함수
function applyForMonth (date) {
  try {
    if (typeof date !== 'number') {
      throw new TypeError('숫자로 입력해주세요.');
    }
    if (date < 1 || date > 12) {
      throw new RangeError('유효한 월을 입력해주세요.');
    }

    console.log(`${date}월 당번으로 등록되셨습니다.`);

  } catch (e) {
    console.error('🛑 에러 발생!', e);
    console.log('다시 등록해주세요.');
  }
}
applyForMonth('5');

applyForMonth(13);

각각 에러가 발생한다. 컴퓨터는 13이 들어갔을때 이것이 오류라는 것을 인지하지 못하기 때문에 수동적으로 오류를 발생시킨 것이다. 

 

III. 에러 버블링 error bubbling

다른 함수를 호출했을 때

  • 에러 발생시 해당 함수에서 잡지 않으면 호출한 곳으로 던져진다.
  • 다중 호출시 에러를 핸들링하는 코드가 있는 호출자까지 전달된다.
function func1 () {
  throw new Error('에러');
}

function func2 () {
  func1();
}

function func3 () {
  func2();
}

function func4 () {
  try {
    func3();

  } catch (e) {
    console.error(e);
  }
  console.log('실행완료');
}

func4();

func1 에서 에러가 났는데 func1에서 에러를 처리하는 로직이 없어서 func1을 호출한 func2로 애러를 핸들링하는 코드가 전달되고 거기에도 없으니 func2를 호출한 func3로 이동하고 결국 func4까지 가서 에러가 처리된 것을 볼 수 있다. 

이런 현상은 좋지 않으니 에러는 가능한 발생한 곳 가까이서 처리하는 것이 좋다.