본문 바로가기
FrontEnd/JavaScript

[ JavaScript ] 예외 처리 exception handling

by ウリ김영은 2023. 10. 26.

예외 처리란,

코드 실행 중에 예기치 못한 에러가 발생했을 때,

이로부터 코드의 실행 흐름을 복구 하는 기능이다.

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

문법 에러와 같이 프로그래머의 실수를 인해 에러가 발생하는 경우도 있지만,

네트워크 에러와 같이 코드와는 무관한 이유로 발생하는 에러도 있다.

new Array(-1) //RangeError: Invalid array length
console.log(foo) //ReferenceError: foo is not defined
fetch('https://blabla.nowhere') //TypeError: Failed to fetch

코드 실행 중에 에러가 발생하면, 코드의 실행이 중단되어 그 시점에 실행 중이었던 작업을 완료할 수 없게 됩니다.

JavaScript는 이로부터  코드 실행 흐름을 원상복구 할 수 있는 기능이 있는데,

바로 try..catch..finally이다.


try {
  console.log('에러가 나기 직전까지의 코드는 잘 실행됩니다.');
  new Array(-1); // RangeError: Invalid array length
  console.log('에러가 난 이후의 코드는 실행되지 않습니다.');
} catch (e) {
  console.log('코드의 실행 흐름이 catch 블록으로 옮겨집니다.');
  alert(`다음과 같은 에러가 발생했습니다: ${e.name}: ${e.message}`);
}  //finally 는 필수가 아니다.

에러가 났을 때 코드의 흐름이 `try`블록 내부에서 `catch`블록으로 넘어간다.

catch블록 안에서는 에러에 대한 정보를 담고 있는 객체(위에서는 e)를 사용할 수 있다.

사용을 안 한다면 생략도 가능하다.(밑에 코드 참고)


  try {
  new Array(-1); // RangeError: Invalid array length
  } catch {
  alert(`에러가 발생했습니다.`);
  }

try 블록 바로 뒤에 finally 블록이 오면,

finally 블록에 있는 코드는

try 블록 안에서의 에러 발생 여부와 관계 없이 무조건 실행됩니다.

try 내 에서 return, break, continue 등 코드의 실행 흐름이 즉시 이동될 때도 마찬가지다.

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

직접 에러 발생시키기

Error 생성자와 throw 구문을 사용해서 직접 에러를 발생시킬 수 있다.

간혹 프로그램을 작성하면서

에러의 종류를 구분해야 하거나,

에러 객체에 기능을 추가해야 할 필요가 있다.

Error를 상속받는 클래스를 만들어서, throw구문에서 이 클래스를 대신 사용할 수 있다.


 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.value);
  }
}

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

비동기식으로 작동하는 콜백의 내부에서 발생한 에러는,

콜백 바깥에 있는 try 블록으로는 잡아낼 수 없다.

JavaScript 엔진은 에러가 발생하는 순간 호출 스택을 되감는 과정을 거칩니다.

이 과정 중에 try 블록을 만나야 코드의 실행 흐름을 원상복구시킬 수 있다.


try {
  setTimeout(() => {
    throw new Error('에러!');
  });
} catch (e) {
  console.error(e);
}

따라서,

try블록을 비동기 콜백 내부에 작성해줘야 한다.

setTimeout(() => {
  try {
    throw new Error('에러!');
  } catch (e) {
    console.error(e);
  }
});

 

Promise

프로미스 객체는 세 가지 상태를 가진다. 

- pending - Promise 객체에 결과값이 채워지지 않은 상태

- fullfilled - Promise 객체에 결과값이 채워진 상태

- rejected - Promise 객체에 결과값을 채우려고 시도하다가 에러가 난 상태

 

Promise 객체가 rejected 상태가 되면, `then` 메소드에 첫 번째 인수로 넘겨준 콜백이 실행되지 않고, 

두 번째 인수로 넘겨준 콜백이 대신 실행됩니다. 

그리고 이 콜백에는 에러 객체가 첫 번째 인수로 주어집니다. 

const p = new Promise(resolve => {
  const even = parseInt(prompt('짝수를 입력하세요'));
  if (even % 2 !== 0) {
    throw new Error('짝수가 아닙니다.');
  } else {
    resolve(even);
  }
});

p.then(even => {
  return '짝수입니다.';
}, e => {
  return e.message;
}).then(alert);

아니면 

`catch`메소드를 통해 에러 처리 콜백을 지정할 수도 있다.

p.then(even => {
  return '짝수입니다.';
}).catch(e => {
  return e.message;
}).then(alert);

 

 

만약, 

`then` 메소드의 안에서 에러가 발생하면, 

`try ... catch`구문과 유사하게 

처음 만나는 에러 처리 콜백으로 코드의 실행 흐름이 건너뛴다. 

Promise.resolve()
  .then(() => {
    throw new Error('catch 메소드를 통해 예외 처리를 할 수 있습니다.');
  })
  .then(() => {
    console.log('이 코드는 실행되지 않습니다.');
  })
  .catch(e => {
    return e.message;
  })
  .then(console.log);

 

 

 

 

 

비동기 함수

비동기 함수 내부에서는,

rejected 상태가 된 Promise 객체를

동기식 예외 처리 방식과 동일하게 `try...catch..finally` 구문으로 처리할 수 있다

 

단, Promise객체에 대해 `await`구문을 사용하지 않는 경우,

에러가 발생해도 `catch` 블록으로 코드의 실행 흐름이 이동하지 않는다. 

 

async function func() {
  try {
    fetch('https://nonexistent-domain.nowhere');
  } catch (e) {
    console.log(e.message);
  }
}

func(); // 아무것도 출력되지 않습니다.

참조

https://helloworldjavascript.net/pages/290-exception.html