Javascript try catch 개발 방법

Javascript try catch 개발 방법

예외(exception)란 프로그램이 실행 중에 발생하는 런타임 오류를 의미한다.

이러한 예외가 발생하지 않도록 미리 방지하는 것도 중요하지만, 발생한 예외를 처리하는 방법 또한 매우 중요하다.

오류(error)란 프로그램 구문의 문법적인 오류를 의미합니다.
Javascript try catch 개발 방법

예외 처리(exception handling)

Javascript try catch 개발 방법

자바스크립트에서는 프로그램이 실행되는 도중 발생하는 예외를 처리하기 위해 try / catch / finally 문을 사용한다.    

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
} finally {
  // 무조건 실행되는 코드
}

- try: 에러가 났을 때 원상복구를 시도할 코드. 에러 발생시 코드의 실행 흐름이 catch 블록으로 옮겨간다.

- catch: 에러에 대한 정보를 담고 있는 객체(위 예제의 e)를 사용할 수 있다.

e.name
에러 이름. 정의되지 않은 변수 때문에 발생한 에러라면 "ReferenceError"가 이름이 됩니다.

e.message
에러 상세 내용을 담고 있는 문자 메시지
표준은 아니지만, name과 message 이외에 대부분의 호스트 환경에서 지원하는 프로퍼티도 있습니다.

e.stack
현재 호출 스택. 에러를 유발한 중첩 호출들의 순서 정보를 가진 문자열로 디버깅 목적으로 사용됩니다.

- finally: try블록 안에서의 에러 발생 여부와 관계 없이 무조건 실행되어야 하는 코드

return, break, continue등으로 코드의 실행 흐름이 즉시 이동되더라도 무조건 실행된다.

finally블록은 catch 블록과도 같이 사용된다.

  • 에러가 안 났을 때: try - finally
  • 에러가 났을 때: try - 에러발생 - catch - finally

 - throw: 예외를 발생시키다라는 것은 에러나 예외 상황을 알린다는 뜻이고, 예외를 강제로 발생시켜야 할 경우가 생길 때는 throw를 사용한다.

try..catch..finally 안의 변수는 지역 변수입니다.

finally 절은 try..catch 절을 빠져나가는 어떤 경우에도 실행됩니다. return을 사용해 명시적으로 빠져나가려는 경우도 마찬가지입니다.

catch 블록과 finally 블록은 선택적인 옵션으로 반드시 사용할 필요는 없습니다.

따라서 사용할 수 있는 모든 적합한 try 구문은 다음과 같습니다.

1. try / catch

2. try / finally : try..finally 안에선 에러를 처리하고 싶지 않지만, 시작한 프로세스가 마무리되었는지 확실히 하고 싶은 경우에 사용합니다.

3. try / catch / finally

try {
    * 실행할 코드를 입력한다.
    * 정상이라면 문제없이 실행된다. 하지만 경우에 따라 예외가 발생할 수 있다. 
    * 예외는 throw 문에 의해 발생할 수도 있고, 예외를 발생시키는 메서드에 의해 발생할 수도 있다.
} catch (e) {
    * 이 블록 내부의 문장들은 오직 try 블록에서 예외가 발생할 경우에만 실행된다. 
    * 이 문장들에선 지역 변수 e를 사용하여 Error 객체 또는 앞에서 던진 다른 값을 참조할 수 있다. 
    * 이 블록에서는 어떻게든 그 예외를 처리할 수도 있고,
    * 그냥 아무것도 하지 않고 예외를 무시할 수도 있고, 
    * 아니면 throw 를 사용해서 예외를 다시 발생시킬 수도 있다. 
} finally {
    * 이 블록에는 try 블록에서 일어난 일에 관계없이 무조건 실행될 코드가 위치한다. 
    * 이 코드는 try 블록이 어떻게든 종료되면 실행된다. 
    * try 블록이 종료되는 상황은 다음과 같다. 
        1) 정상적으로 블록의 끝에 도달했을 때 
        2) break, continue 또는 return 문에 의해서 
        3) 예외가 발생했지만 catch 절에서 처리했을 때 
        4) 예외가 발생했고 그것이 잡히지 않은 채 퍼져나갈 때 
}

🔥 예외 자세하게

자바와는 달리 자바스크립트는 변수가 죄다 Object로 통일이다.

따로 자료형을 명시하지 않으므로 항상 e instanceof userException을 통해 객체 비교를 해서 따져 야 한다.

function userException(name, message) {
    this.name = name;
    this.message = message;
}

function exceptionHandling() {
    try {
        var a = 1;
        var b = 0;
        
        if( b == 0) {
            throw new userException('divideByZeroException', '0으로 나눌 수 없습니다');
        }
    } catch(e) {
        if(e instanceof userException) {
            console.log(e.name + "::" + e.message);
        }
        if(e instanceof dataValidationException) {
            console.log(e.name + "::" + e.message);
        }
    } finally {
        console.log("finally excute");
    }
}

🔥 finally 반드시 써야되?

Q. 현재 상황은 에러의 유무와 상관없이, 작업 후 초기화를 해야합니다. finally를 사용하면 이점이 있을까요? 아니면 두 코드 조각은 동일하게 동작할까요?

try {
  작업
} catch (e) {
  에러 핸들링
} finally {
  작업 내역 삭제
}
try {
  작업
} catch (e) {
  에러 핸들링
}

작업 내역 삭제

A. 우선 두 코드는 다르게 동작된다. try문안에 작업이 에러가 뜨면 catch에서 핸들링을 하고 스크립트가 종료되게 된다. 하지만 finally안에다 명시를 해주면 스크립트 종료되기전에 반드시 삭제가 실행되게 된다.


예외 처리와 Call Stack

자바스크립트 인터프리터는 함수의 호출 과정을 모두 추적하고 있다.

function a() {
	console.log('a: calling b');
	b();
	console.log('a: done');
}
function b() {
	console.log('b: calling c');
	c();
	console.log('b: done');
}
function c() {
	console.log('c: throwing error');
	throw new Error('c error');
	console.log('c: done');
}
function d() {
	console.log('d: calling c');
	c();
	console.log('d: done');
}


try {
	a();
} catch (err) {
	console.log(err.stack);
}
try {
	d();
} catch (err) {
	console.log(err.stack);
}

함수 a에서 함수 b를 호출하고 함수 b에서는 함수 c를 호출한다면, 함수 c가 실행을 마칠 때 실행 흐름은 함수 b로 돌아가고 b가 실행을 마칠 때 실행 흐름은 함수 a로 돌아간다.

그렇기 때문에 c가 실행 중일 때는 a와 b는 완료될 수 없고 이렇게 완료되지 않은 함수가 쌓이는 것을 Call Stack 이라 부른다.

에러는 콜 스택 어디에서든 캐치할 수 있다. 어딘가에서 일어나는 에러를 캐치하지 않으면 자바스크립트 인터프리터는 프로그램을 멈추게 되고 처리하지 않은 예외 때문에 프로그램이 충돌하는 원인이 된다.

에러를 캐치하면 콜 스택에서 문제 해결에 유용한 정보를 얻을 수 있다.

만약 함수 c에서 에러가 일어났다면, 콜 스택은 c에서 일어난 에러를 보고하는 데 그치지 않고 b가 c를 호출했으며, b는 a에서 호출했다는 것도 함께 알려주기 때문에 디버그에 유용하게 사용된다.

실행결과

a: calling b

b: calling c

c: throwing error

Error: c error  // 에러가 발생한 콜 스택

스택을 추적한 결과를 보여주고 가장 깊은 함수에서 시작하고 함수가 남지 않았을 때 끝난다.


예외 발생 throw

예외를 발생시킨다는 것은 명시적으로 오류를 발생시킨다는 의미뿐만 아니라 예외 상황을 알린다는 의미도 있다.

코드를 다른 사람이나 미래의 내가 의도한 대로 사용하지 않을 경우 에러가 발생하도록 할 수 있다.

- Error생성자, throw구문

throw new Error();

try {
  const even = parseInt(prompt('짝수를 입력하세요'));
  if (even % 2 !== 0) {
    throw new Error('짝수가 아닙니다.');
  }
} catch (e) {
  alert(e.message);
}
// 3을 입력할 경우
// Error: 짝수가 아닙니다.

자체 Error클래스

복잡한 프로그램을 짜다보면 추가적인 자세한 정보를 추가해서 에러를 만들고 싶게 된다.

에러의 종류를 구분해야 하거나 에러 객체에 기능을 추가해야 할 필요가 있다.

그냥 내장 에러 생성자가 아니라 자체 Error 클래스를 만들경우 다음과 같은 방법이 있다.

class MyError extends Error { //에러 상속
  constructor(value, ...params) {
    super(...params);
    this.value = value;
    this.name = 'MyError';
  }
}


try {
  const even = parseInt(prompt('짝수를 입력하세요'));
  if (even % 2 !== 0) {
    throw new MyError(even, '짝수가 아닙니다.');
  }
} catch (e) {
  if (e instanceof MyError) {
    console.log(`${e.name}: ${e.value} 는 ${e.message}`);
  }
}

한번 throw된 에러를 캐치하면 다른곳의 try의 catch문은 실행 되지 않는다.

한번 throw된걸 이미 캐치했으니까.

근데, catch나 finally에서 다시 재 throw한다면 바깥 try{}문에서 또 잡힐 것이다.

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 ); // 에러를 잡음
}


비동기식 코드에서의 예외 처리

setTimeout

try..catch는 기본적으로 동기적으로 동작한다.

setTimeout처럼 ‘스케줄 된(scheduled)’ 코드에서 발생한 예외는 try..catch에서 잡아낼 수 없다.

try {
  setTimeout(function() {
     noSuchVariable; // 스크립트는 여기서 죽습니다.
  }, 1000);
} catch (e) { // 스크립트는 이미 멈췄기 때문에, 비동기 환경에서 뭐가 됬든 작동 안함.
  alert( "작동 멈춤" );
}

setTimeout에 넘겨진 익명 함수는 엔진이 try..catch를 떠난 다음에서야 실행되기 때문이다.

스케줄 된 함수 내부의 예외를 잡으려면, try..catch를 반드시 함수 내부에 구현해야 한다.

setTimeout(function() {
  try {
    noSuchVariable; // 이제 try..catch에서 에러를 핸들링 할 수 있습니다!
  } catch {
    alert( "에러를 잡았습니다!" );
  }
}, 1000);

Promise

Promise 객체는 세 가지 상태를 가질 수 있다.

pending - Promise 객체에 결과값이 채워지지 않은 상태
fulfilled - Promise 객체에 결과값이 채워진 상태(이때 then메소드 또는 await를 통해 무언가를 실행했다.)
rejected- Promise 객체에 결과값을 채우려고 시도하다가 에러가 난 상태

then메소드에 첫 번째 인수로 넘겨준 콜백이 실행되지 않고, 두 번째 인수로 넘겨준 콜백이 실행된다. 그리고 이 콜백에는 에러 객체가 첫번째 인수로 주어진다.

const p = new Promise(resolve => {
  const even = parseInt(prompt('짝수를 입력하세요'));
  if (even % 2 !== 0) {
    throw new Error('짝수가 아닙니다.');
  } else {
    // 짝수면 fullfiled되어 then메소드의 첫번째 인수로 들어간 함수가 실행
    resolve(even);
  }
});

// then 콜백에서 반환된 값이 다음Promise의 값이 된다.
p.then(even => {
  return '짝수입니다.'; // resolve() 됬을 경우
}).catch(e => {
  return e.message; // throw 됬을 경우
}).then(alert); // 앞서 리턴값을 alert의 인자값으로 대입

Promise가 rejected 상태가 되었을 때 catch 메소드를 통해 다음과 같은 방법으로도 에러 처리 콜백을 지정해 줄 수 있다.

비동기 함수 async

비동기 코드에서의 try...catch비동기 함수에서의 try...catch는 다르게 동작한다.

(내부 동작 방식이 완전히 다르다. - 비동기 함수를 사용하면 예외처리도 보다 편하게 할 수 있다.)

비동기 함수 내부에서는, rejected 상태가 된 Promise객체를 동기식 예외처리 방식과 동일하게 try...catch...finally 구문으로 처리할 수 있다.

async function func() {
  try {
    // ※ 단, Promise 객체에 대해 await 구문을 사용해야만 
    // 에러가 발생했을 때 catch 블록으로 코드의 실행 흐름이 이동한다.
    const res = await fetch('https://nonexistent-domain.nowhere');
  } catch (e) {
    console.log(e.message);
  }
}

func(); // 출력 결과: Failed to fetch

Reference

https://tcpschool.com/javascript/js_exception_exception

https://chiabi.github.io/2018/06/11/try-catch/

https://velog.io/@smooth97/-Learning-Javascript-%EC%98%88%EC%99%B8%EC%99%80-%EC%97%90%EB%9F%AC-%EC%B2%98%EB%A6%AC

https://gangzzang.tistory.com/entry/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8JavaScript-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%ACException-handling

https://ktko.tistory.com/entry/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%98%88%EC%99%B8%EC%B2%98%EB%A6%AC

https://ko.javascript.info/try-catch