47장 에러처리


에러 처리의 필요성

발생한 에러를 대처하지 않고 방치하면 프로그램 강제 종료된다.
→ 에러처리가 중요하다.

try…catch 문을 이용해서 에러에 대응할 수 있다.

console.log('[Start]');

try {
  foo();
} catch (error) {
  console.error('[에러 발생]', error);
  // [에러 발생] ReferenceError: foo is not defined
}

// 발생한 에러에 적절한 대응을 하면 프로그램이 강제 종료되지 않는다.
console.log('[End]');

그러나, 직접적인 에러를 발생하지 않는 예외적인 상황이 발생할 수도 있다.
예를 들면 에러를 발생시키지 않고 Null을 반환할 수도 있다.

// DOM에 button 요소가 존재하는 경우 querySelector 메서드는 에러를 발생시키지 않고 null을 반환한다.
const $button = document.querySelector('button'); // null
$button?.classList.add('disabled');

이 경우에는 단축평가 or 옵셔널 체이닝 연산자를 활용해서 에러를 처리할 수도 있을 것이다.
이렇게 다양한 상황에서 예외가 발생할 수 있고, 그것들에 대응하는 코드를 작성하는 것이 중요하다.

try … catch … finally 문

앞서 말한 것 처럼 에러처리 구현하는 방법은 크게 2가지가 있다.

  1. 예외적인 상황의 반환 값 (null or -1) 을 단축 평가 또는 옵셔널 체이닝 연산자로 처리
  2. try … catch … finally 문
try {
	//실행할 코드(에러가 발생할 가능성이 있는 코드)
} catch (err) {
	//try 코드 블록에서 에러가 발생하면 이 코드 블록의 코드가 실행된다.
	//err에는 try 코드 블록에서 발생한 Error 객체가 전달된다.
} finally {
	//에러 발생과 상관없이 반드시 한 번 실행된다.
}

Error 객체

Error 생성자 함수는 에러 객체를 생성. 에러를 설명하는 에러 메시지를 인수로 전달 할 수 있다.

const error = new Error('invalid');

에러 객체는 message 프로퍼티와 stack 프로퍼티를 갖는다.
message : Error 생성자 함수에서 인수로 전달한 에러 메시지
stack : 에러를 발생시킨 콜 스택의 호출 정보를 나타내는 문자열. 디버깅 목적으로 사용

JS에서는 7가지 에러 생성자 함수가 있음
모두 Error.prototype을 상속 받음.

Untitled 39.jpeg

1 @ 1;    // SyntaxError: Invalid or unexpected token
foo();    // ReferenceError: foo is not defined
null.foo; // TypeError: Cannot read property 'foo' of null
new Array(-1); // RangeError: Invalid array length
decodeURIComponent('%'); // URIError: URI malformed

throw 문

에러 객체 생성한다고 에러가 발생하는 건 아님.

try {
  // 에러 객체를 생성한다고 에러가 발생하는 것은 아니다.
  new Error('something wrong');
} catch (error) {
  console.log(error);
}

그럼 어떻게 에러를 발생시키는 건데?
→ throw 문으로 에러 객체를 던져야 한다.

try {
  // 에러 객체를 던지면 catch 코드 블록이 실행되기 시작한다.
  throw new Error('something wrong');
} catch (error) {
  console.log(error);
}

코드를 보면 throw로 에러를 던지면 catch문에서 error에 에러 객체가 할당 된다.

예를 들어 콜백함수를 호출하는 Repeat 함수 안에 throw 에러문이 있다면 try 코드 블록 내에서 사용해야 한다

// 외부에서 전달받은 콜백 함수를 n번만큼 반복 호출한다
const repeat = (n, f) => {
  // 매개변수 f에 전달된 인수가 함수가 아니면 TypeError를 발생시킨다.
  if (typeof f !== 'function') throw new TypeError('f must be a function');

  for (var i = 0; i < n; i++) {
    f(i); // i를 전달하면서 f를 호출
  }
};

try {
  repeat(2, 1); // 두 번째 인수가 함수가 아니므로 TypeError가 발생(throw)한다.
} catch (err) {
  console.error(err); // TypeError: f must be a function
}

에러의 전파

에러는 호출자 방향으로 전파된다.
→ 콜 스택의 아래 방향으로 전파.

const foo = () => {
  throw Error('foo에서 발생한 에러'); // ④
};

const bar = () => {
  foo(); // ③
};

const baz = () => {
  bar(); // ②
};

try {
  baz(); // ①
} catch (err) {
  console.error(err);
}

foo에서 콜 스택 아래 방향으로 전파되고 위 코드에서는 전역에서 catch 된다.

Untitled 1 25.jpeg|300

throw된 에러를 어디에서도 catch 하지 않으면 프로그램은 강제 종료된다.

reference