티스토리 뷰

10.1 Erro handling, "try...catch"

try...catch 구문은 자바와 거의 동일하다. catch문에 예외 타입을 명시하지 않는 것 정도?가 차이이겠다. 'parse-time 에러'가 발생할 경우 즉, 코드 구문 분석 과정에서 발생하는 에러는 try...catch가 동작하지 않는다. 오직 runtime error에만 동작한다. 또한, 동기적으로 동작하기 때문에 setTimeout과 같이 try...catch를 지나간 후에 발생하는 에러는 잡지 못한다.

 

parse-time 에러는 Java에서의 compile error와 비슷하게 코드 실행 전에 발생하는 에러이다. 디테일한 차이가 분명히 있긴 하지만 현재로서는 요정도로 생각해도 좋을 것 같다.

Error object

javascript는 에러 발생 시 에러 내용이 담긴 객체를 생성하고 catch에 이 객체를 인수로 전달한다. 에러 객체는 name, message 속성이 주요하게 사용되고, 비표준 속성으로 stack이 있다. 예시를 참고하자.

try {
  lalala; // 에러, 변수가 정의되지 않음!
} catch(err) {
  alert(err.name); // ReferenceError
  alert(err.message); // lalala is not defined
  alert(err.stack); // ReferenceError: lalala is not defined at ... (호출 스택)

  // 에러 전체를 보여줄 수도 있습니다.
  // 이때, 에러 객체는 "name: message" 형태의 문자열로 변환됩니다.
  alert(err); // ReferenceError: lalala is not defined
}

catch에 자세한 정보가 필요 없을 때는 객체 인수를 생략할 수도 있다.

try {
  // ...
} catch { // <-- (err) 없이 쓸 수 있음
  // ...
}

Using "try...catch"

사용방식은 Java에서와 거의 동일하기 때문에, 예시만 보고 넘어가자.

let json = "{ bad json }";

try {

  let user = JSON.parse(json); // <-- 여기서 에러가 발생하므로
  alert( user.name ); // 이 코드는 동작하지 않습니다.

} catch (e) {
  // 에러가 발생하면 제어 흐름이 catch 문으로 넘어옵니다.
  alert( "데이터에 에러가 있어 재요청을 시도합니다." );
  alert( e.name );
  alert( e.message );
}

Throwing our own errors
필요에 따라 에러를 던지고 싶을 수도 있다. 이럴 때 throw 연산자를 사용하면 된다. 에러 객체로는 어떤 객체든지 상관없지만, 가능한 name, message 속성이 포함되어 있는 것이 좋다.

throw <error object>

// -----------

let error = new Error(message);
// or
let error = new SyntaxError(message);
let error = new ReferenceError(message);
let json = '{ "age": 30 }'; // 불완전한 데이터

try {

  let user = JSON.parse(json); // <-- 에러 없음

  if (!user.name) {
    throw new SyntaxError("불완전한 데이터: 이름 없음"); // (*)
  }

  alert( user.name );

} catch(e) {
  alert( "JSON Error: " + e.message ); // JSON Error: 불완전한 데이터: 이름 없음
}

Rethrowing

아래 예시처럼 catch를 통해 에러메시지를 결정하면, 에러 종류에 관계없이 동일한 방식으로 에러를 처리하게 될 수도 있다. 이때 에러를 다시 던지는 방식으로 처리하면 된다. 

let json = '{ "age": 30 }'; // 불완전한 데이터

try {
  user = JSON.parse(json); // <-- user 앞에 let을 붙이는 걸 잊었네요.

  // ...
} catch(err) {
  alert("JSON Error: " + err); // JSON Error: ReferenceError: user is not defined
  // (실제론 JSON Error가 아닙니다.)
}

에러 분석 예시

try {
  user = { /*...*/ };
} catch(err) {
  if (err instanceof ReferenceError) {
    alert('ReferenceError'); //  정의되지 않은 변수에 접근하여 'ReferenceError' 발생
  }
}

// ----------------

let json = '{ "age": 30 }'; // 불완전한 데이터
try {

  let user = JSON.parse(json);

  if (!user.name) {
    throw new SyntaxError("불완전한 데이터: 이름 없음");
  }

  blabla(); // 예상치 못한 에러

  alert( user.name );

} catch(e) {

  if (e instanceof SyntaxError) {
    alert( "JSON Error: " + e.message );
  } else {
    throw e; // 에러 다시 던지기 (*)
  }

}

(*) 줄에서 에러가 다시 던져진다. 이때, 바깥에 try...catch문이 있다면 다시 던져진 에러를 처리할 수 있다.

function readData() {
  let json = '{ "age": 30 }';

  try {
    // ...
    blabla(); // 에러!
  } catch (e) {
    // ...
    if (!(e instanceof SyntaxError)) {
      throw e; // 알 수 없는 에러 다시 던지기
    }
  }
}

try {
  readData();
} catch (e) {
  alert( "External catch got: " + e ); // 에러를 잡음
}

try...catch...finally

finally라는 구문을 하나 더 사용할 수 있다. 이 부분도 자바와 같다. try문 안의 실행 결과와는 상관없이 공통적으로 코드를 수행하고 싶을 때 사용한다. 

let num = +prompt("양의 정수를 입력해주세요.", 35)

let diff, result;

function fib(n) {
  if (n < 0 || Math.trunc(n) != n) {
    throw new Error("음수나 정수가 아닌 값은 처리할 수 없습니다.");
  }
  return n <= 1 ? n : fib(n - 1) + fib(n - 2);
}

let start = Date.now();

try {
  result = fib(num);
} catch (e) {
  result = 0;
} finally {
  diff = Date.now() - start;
}

alert(result || "에러 발생");

alert( `연산 시간: ${diff}ms` );

try...catch 절에서 return이 있더라도 finally 절은 수행된다.

function func() {

  try {
    return 1;

  } catch (e) {
    /* ... */
  } finally {
    alert( 'finally' );
  }
}

alert( func() ); // finally 안의 alert가 실행되고 난 후, 실행됨

Global catch

try...catch 조차 잡을 수 없는 에러가 발생하는 경우 외부 환경을 이용해 처리할 수도 있다. Node.js의 process.ong("uncaughtException")이나 브라우저에서 window.onerror가 있다. 이 부분은 참고 삼아 확인해 보면 좋을 것 같다. 

댓글
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday