본문 바로가기

Front-end/JavaScript

[JavaScript]프라미스와 에러 핸들링

프라미스가 거부되면 제어 흐름이 제일 가까운 rejection 핸들러로 넘어가기 때문에 프라미스 체인을 사용하면 에러를 쉽게 처리할 수 있다.

fetch('https://no-such-server.blabla') // 존재하지 않는 url일 때.
  .then(response => response.json())
  .catch(err => alert(err)) // 실행흐름이 이 .catch로 넘어가면서 에러를 alert한다.

여기서 .catch는 첫번째핸들러일 필요는 없고 여러개의 .then뒤에도 올 수 있다. 가장 먼저 오는 .catch에서 에러를 처리한다.

1. 암시적 try...catch

프라미스 executor와 프라미스 핸들러 코드 주위엔 '보이지 않는(암시적) try..catch'가 있다. 예외가 발생하면 암시적 try..catch에서 예외를 잡고 이를 reject처럼 다룬다.

무슨말이냐?

new Promise((resolve, reject) => {
  throw new Error("에러 발생!");
}).catch(alert); 
//try...catch 가 명시적으로 보이지 않지만, try{..}안에 resolve를 실행하고 
//.catch{...}안에 reject 처리로직을 실행한다.
// try...catch문에서 스스로 에러를 잡고 (throw new Error("에러 발생!");) 이를 catch문으로 넘김.

위의 예시는 executor 함수뿐만 아니라 핸들러에서도 발생한다. .then 핸들러 안에서 throw를 사용해 에러를 던지면, 이 자체가 거부된 프라미스를 의미하게 되는데, 따라서 제어 흐름이 가장 가까운 에러 핸들러로 넘어간다.

new Promise((resolve, reject) => {
  resolve("OK");
}).then((result) => {
  throw new Error("에러 발생!"); // 프라미스가 거부됨
}).catch(alert); // Error: 에러 발생!

throw문이 만든 에러뿐만 아니라 모든 종류의 에러가 암시적 try..catch에서 처리된다. 

 

2. 다시 던지기

체인 마지막의 .catch는 try..catch와 유사한 역할을 한다. 

.then 핸들러를 원하는 만큼 사용하다 마지막에 .catch 하나만 붙이면 .then 핸들러에서 발생한 모든 에러를 처리할 수 있다.

일반 try..catch에선 에러를 분석하고, 처리할 수 없는 에러라 판단되면 에러를 다시 던질 때가 있는데, 프라미스에도 유사한 일을 할 수 있다.

.catch 안에서 throw를 사용하면 제어 흐름이 가장 가까운 곳에 있는 에러 핸들러로 넘어간다. 여기서 에러가 성공적으로 처리되면 가장 가까운 곳에 있는 .then 핸들러로 제어 흐름이 넘어가 실행이 이어진다.

// 실행 순서: catch -> then
new Promise((resolve, reject) => {

  throw new Error("에러 발생!");

}).catch(function(error) {

  alert("에러가 잘 처리되었습니다. 정상적으로 실행이 이어집니다.");

}).then(() => alert("다음 핸들러가 실행됩니다."));

.catch 블록이 정상적으로 종료되었기 때문에 다음 성공 핸들러 .then이 호출된 것이다.

// 실행 순서: catch -> catch
new Promise((resolve, reject) => {

  throw new Error("에러 발생!");

}).catch(function(error) { // (*)

  if (error instanceof URIError) {
    // 에러 처리
  } else {
    alert("처리할 수 없는 에러");

    throw error; // 에러 다시 던지기
  }

}).then(function() {
  /* 여기는 실행되지 않습니다. */
}).catch(error => { // (**)

  alert(`알 수 없는 에러가 발생함: ${error}`);

});

3. 처리되지 못한 거부

에러를 처리하지 못하면?

new Promise(function() {
  noSuchFunction(); // 존재하지 않는 함수
})
  .then(() => {
    // 성공상태의 프라미스를 처리하는 핸들러
  }); // 끝에 .catch가 없음!
에러가 발생하면 프라미스는 거부상태가 되고, 실행 흐름은 가장 가까운 rejection 핸들러로 넘어간다. 그런데 위 예시엔 예외를 처리해 줄 핸들러가 없어서 에러가 ‘갇혀버린다’. 에러를 처리할 코드가 없기 때문에!!
 
자바스크립트 엔진은 프라미스 거부를 추적하다가 위와 같은 상황이 발생하면 전역 에러를 생성한다. 그리고 브라우저 환경에선 이런 에러를 unhandledrejection 이벤트로 처리할 수 있다.
window.addEventListener('unhandledrejection', function(event) {
  // unhandledrejection 이벤트엔 두 개의 특수 프로퍼티가 있다.
  alert(event.promise); // [object Promise] - 에러를 생성하는 프라미스
  alert(event.reason); // Error: 에러 발생! - 처리하지 못한 에러 객체
});
 

브라우저 환경에선 에러가 발생했는데 .catch가 없으면 unhandledrejection 핸들러가 트리거 된다. unhandledrejection 핸들러는 에러 정보가 담긴 event 객체를 받기 때문에 이 핸들러 안에서 원하는 작업을 할 수 있다.

대개 이런 에러는 회복할 수 없기 때문에 개발자로서 할 수 있는 최선의 방법은 사용자에게 문제 상황을 알리고 가능하다면 서버에 에러 정보를 보내는 것이다.