티스토리 뷰

사이드 프로젝트를 진행하면서 api 통신을 하는 경우가 많았습니다. 이로 인해 에러를 처리하는 코드를 많이 작성하다 보니 문득 아래와 같은 의문이 생겼습니다.

에러가 만약 중첩된 함수에서 일어나고 외부 함수에서 try catch 문으로 처리를 해주어도 에러를 잡아낼 수 있을까? 에러란 무엇이지?...에.....러!? 😇 에러에 대해 공부해 봐야겠군..!

에러에 대해 공부를 결심하고 이번 기회에 정리해보기로 했습니다. 그리고 정리된 내용을 저 처럼 에러 처리에 대해 어려워 하신 분들을 위해 글을 작성하게 되었습니다.

이 글에서 알 수 있는 내용은 다음과 같습니다.

  • 에러란 무엇일까?
  • 에러의 종류,
  • 중첩된 에러 핸들링,
  • 동기적인 코드에서 발생하는 에러와 비동기적인 코드에서 발생하는 에러를 잡는 방법에 대한 차이

TLDR

에러가 발생할 시점을 call stack에 그리면 해결법을 찾을 수 있습니다.

1. 에러(error) who are you?

먼저 에러(error)란 무엇일까요?

에러는 프로그래밍 시 예기치 못한 상황이 발생해 프로그램에 더 큰 문제가 생기지 않기 위해 발생되는 것입니다.

자바스크립트에서는 2가지 상황에서 에러가 던져집니다.

  1. 첫 번째로는 자바스크립트 런타임 시에 발생하는 에러가 있습니다
  1. 두 번째로는 개발자가 정의한 예외 상황으로 인해 발생하는 에러가 있습니다.

두번재 방법인 개발자가 에러를 생성하는 방법은 다음과 같습니다.

new Error(message, option)

위의 기본 error constructor뿐만 아니라 자바스크립트에서 제공하는 추가적인 constructors들이 있습니다.

2. Error의 constructors종류

1.EvalError

  • eval함수를 사용할때 발생하는 에러 인스턴스 입니다.

2. RangeError

  • 숫자 변수나 파라미터가 범위를 넘었을대 표현하기 위한 애러 객체 입니다.

3. ReferenceError

  • 유효하지 않은 변수나 객체를 참조했을때를 표현하기 위한 에러 객체 입니다.

4. SyntaxError

  • 문법적인 에러를 표현하기위해 존재하는 에러 객체 입니다.

5.TypeError

  • 유효하지 않은 타입의 변수를 표현할기 위한 객체 입니다.

6.URIError

  • encodeURI 혹은 decodeURI에 유효하지 않은 파라미터를 전달했음을 표현하기 위한 객체 입니다.

원하는 custom 에러를 만드는 자세한 방법은 링크를 확인해주세요!

3. 중첩된 함수에서 발생한 에러 처리 [동기식]

다음 그림과 같이 main.js 안에 foo 함수가 있습니다. 그리고 foo 함수 안에서 fee 함수를 호출합니다. 만약 fee에서 에러가 발생했을 때 foo 대신 foo를 호출한 main.js에서 에러를 처리하면 어떻게 될까요?

//main.js
function foo() {
  function fee() {
    throw new Error("error is made in fee");
  }
  fee();
}

try {
  foo();
} catch (e) {
  console.log(e.message);
}

위 코드를 실행하고 나면

error is made in fee

error를 main.js의 catch 문에서 에러를 핸들링 한 것을 확인 할 수 있습니다. 위 과정을 함수 call stack으로 도식화를 해보면 다음 아래 이미지와 같습니다.

(1) 위처럼 try block 안에서 foo가 호출됩니다.

(2) 호출된 함수 foo 안에서 함수 fee가 호출됩니다.

(3) 호출된 함수 fee 에서 throw new Error("error is made in fee"); 를 실행합니다.

이때 에러가 발생하는 코드가 try로 감싸진 call stack 내에서 실행되었기에 에러를 처리할 수 있습니다.

(4) error를 처리하는 catch 문의 코드가 실행됩니다.

에러를 발생한 것이 중첩된 함수에서 실행되었지만. 사실은 같은 하나의 call stack에서 쌓여 실행됩니다. 이로 인해 중첩된 함수에서 발생한 error의 경우 밖에서 에러를 처리하여도 에러 처리가 가능합니다.

💡
결국 중요한 사실은 에러를 발생하는 함수의 위치가 중요한 게 아니라 에러가 발생하는 시점의 call stack이 중요함을 알 수 있습니다.

그렇다면 만약 비동기적으로 에러가 발생할 경우에는 어떻게 될까요?

4. 비동기적으로 발생한 에러를 동기식 호출에서 처리한다면?

function foo() {
  function feePromise() {
    return Promise.reject(new Error("error made in feePromise"));
  }
  feePromise();
}

try {
  foo()
} catch (e) {
  console.log(e.message)
}

다음과 같이 main.js안에 foo함수가 있습니다. 동기식으로 실행되는 foo함수 그 안에 Promise객체를 반환하는 feePromise함수가 있습니다.

main.js에서 위의 예시처럼 동기적으로 실행되는 함수 foo를 try문에서 호출하고 비동기적으로 발생하는 에러를 처리하면 어떻게 될까요?

Uncaught (in promise) Error: error made in feePromise

위처럼 비동기적으로 발생하는 에러를 try catch문으로 잡아내지 못했습니다. 왜 잡아내지 못했을까요? 잡아내지 못한 이유를 call stack 관점에서 생각해 보겠습니다. 아래 그림을 확인해 보세요

  1. 첫 번째로 동기적인 함수 foo가 호출이 됩니다. [동기 시작]
  1. feePromise가 try 문으로 감싸진 call stack 안에서 실행이 됩니다.
  1. Promise.reject가 반환이 됩니다. [비동기]
  1. 상태가 rejected인 반환된 Promise가 microTaskQueue에 추가가 됩니다. [비동기]
  1. try 문으로 실행된 call stack이 모두 호출되어 종료가 됩니다. [동기 종료]
  1. 더 이상 실행시킬 call stack이 없기에 microTaskQueue에 있던 Promise가 호출됩니다.
  1. Error가 발생되었지만 try catch 블록 내에서 실행되지 않았기에 에러를 처리하지 못하고 종료됩니다.

위에서 문제가 된 부분은 바로

5번: try문으로 실행된 call stack이 모두 호출되어 종료가 됩니다. 과 [동기 종료]

7번: Error가 발생되었지만 try catch 블록 내에서 실행되지 않았기에 에러를 처리하지 못하고 종료됩니다. [비동기 코드 실행] 부분 입니다.

💡
즉 동기적으로 실행된 try catch는 비동기 적으로 실행되는 코드보다 먼저 실행되고 종료되기에 비동기 적으로 발생한 에러는 처리할 수 없습니다.

5. 비동기 에러 처리

지금까지 call stack관점에서 본다면 비동기적으로 실행되는 에러는 비동기 적으로 실행되는 call stack에서 처리해주면됩니다. 비동기적으로 에러를 처리하는 방법은 크게 2가지 방법이 있습니다.

  1. Promise가 반환되는 함수를 호출할 때 .catch로 처리해주는 방법이 있습니다.
  1. async 함수 안에서 Promise를 반환되는 함수를 await으로 호출하고 이를 try catch로 처리해줍니다.

1. Promise반환 함수 호출할 때 .catch로 처리하기

function foo() {
  function feePromise() {
    return Promise.reject(new Error("error made in feePromise"));
  }
  feePromise()
    .then(res => console.log(res))
    .catch(err => console.log(err.message));
}

foo()
console.log('동기식 코드 is done')

위처럼 feePromise()를 호출한 부분에 .catch를 활용하면 비동기적으로 발생한 에러를 처리할 수 있습니다. 위를 call stack 관점으로 그려보겠습니다.

  1. foo가 실행됩니다.
  1. feePromise가 실행됩니다.
  1. Promise가 반환되고. microTaskQueue에 추가됩니다. 이때 Promise에는 후에 error가 발생할 때 처리할 수 있는 catch 문이 포함되어 있습니다.
  1. foo 함수가 종료되고 동기식 코드 종료를 알리는 console.log가 실행됩니다.

  1. call stack이 비어있기에 microTaskQueue에 있는 Promise가 call stack으로 이동합니다.
  1. call stack에서 then에서 실행된 (res) => console.log(res)를 실행하기 전 Error가 담긴 Promise를 반환합니다.
  1. 이때 작성했던 .catch로 발생된 에러를 잡습니다. 그리고 microTaskQueue에 반환합니다.
  1. call stack이 비어있기에 에러를 처리하는 (err) => console.log(err.message)를 불러오고 실행시킵니다.
  1. 콘솔창에 error is made in fee가 기록됩니다.

2. async 함수 내에서 try catch 처리하기


async function foo() {
  function feePromise() {
    return Promise.reject(new Error("error made in feePromise"));
  }
  try {
     await feePromise()
  } catch(e) {
   	console.log(e.message)
  }
}

foo()
console.log('동기식 코드 is done')

foo를 async 함수로 바꾸고 feePromise를 호출하는 부분을 await으로 호출합니다.

이렇게 되면 feePromise를 호출하는 부분부터 비동기적으로 코드가 실행이 됩니다. 비동기 적으로 코드를 실행하는 부분을 try catch 내에서 실행하게 되면 비동기적으로 발생한 에러를 해결할 수 있습니다.

이를 한번 call stack 관점에서 도식화 해보겠습니다.

  1. foo를 async 함수를 만들고 호출을 합니다.
  1. await으로 Error를 담은 Promise를 반환하는 feePromise를 호출합니다.
  1. await으로 호출하였기에 feePromise를 호출하고 Promise를 반환하기에 foo는 비동기식으로 작동을 합니다. 따라서 microTaskQueue로 foo는 넘어갑니다.
  1. 이후 console.log('동기식 코드 is done') 이 실행되면서 동기식으로 실행되는 코드가 종료됩니다.

  1. 동기적으로 실행된 call stack이 마무리가 되어 비었기에 microTaskQueue에 있던 함수 foo가 다시 call stack으로 올라갑니다. 그리고 Error를 반환하는 Promise가 실행됩니다.
  1. 이때 try catch문이 반환된 Error를 잡고 console.log(e.message)가 실행됩니다.
  1. 이로 인해 error is made in fee가 콘솔 창에 출력됩니다.
  1. 이후에 함수 foo가 마무리 되어 call stack을 비워냅니다.

지금까지 비동기로 발생한 에러를 처리하는 방법에 대해 알아보았습니다.

비동기적으로 발생한 에러는 비동기적인 코드가 실행될 때 에러 처리를 해야 합니다.

  1. 첫 번째론 Promise를 반환하는 함수를 호출할때 .catch를 활용해서 에러를 잡아냅니다.
  1. 두 번째론 Promise를 호출하는 함수를 async로 함수로 선언하고 Promise를 반환하는 함수를 await으로 호출하고 try catch 문을 활용하면 에러를 처리할 수 있습니다.

6. 마무리 (call stack을 그리자!)

이번 시간에 에러에 대해서 알아보고 동기적으로 발생한 에러와 비동기적으로 발생한 에러를 처리하는 방법에 대해 각각 알아보았습니다.

결국 에러를 처리할때 에러가 발생하는 부분이 어느 위치에서 선언된 것보단 에러가 call stack 관점에서 어느 시점에 발생하는지가 중요함을 알게 되었습니다.

call stack관점으로 분석하면 동기적으로 발생한 에러와 비동기적으로 발생한 에러를 처리해야하는 부분을 쉽게 분석할 수 있습니다.

만약 에러를 처리할 상황이 생기게 된다면 자바스크립트 호출 call stack을 그려서 분석해 본다면 까다롭기만 했던 에러가 프로그램을 지켜주는 좋은 수호자가 될 것입니다.

지금까지 긴 글 읽어주셔서 감사합니다.

7. references

error에 대한 정보 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/Errorhttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error

javascript callstack 시각화 https://dev.to/lydiahallie/javascript-visualized-promises-async-await-5gke

error 처리 관련 좋은글 https://kinsta.com/blog/errors-in-javascript/


Uploaded by N2T

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/08   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
글 보관함