비동기 함수를 사용할 때 에러 처리

모성종·2024년 8월 8일
0

비동기 함수(Promise, async/await)를 사용할 때 에러를 처리하는 방법

  • Promise chaining 시 에러 처리
  • try-catch 구문 사용 시 에러 처리
  • await 로 평가할 때

0. 사용할 비동기 함수

const resolved = (arg, delay = 100) =>
  new Promise((resolve) =>
    setTimeout(() => {
      console.log("resolved", { arg });
      resolve(arg);
    }, delay),
  );
const rejected = (arg, delay = 100) =>
  new Promise((_, rejected) =>
    setTimeout(() => {
      console.log("rejected", { arg });
      rejected(arg);
    }, delay),
  );

1. Promise chaining 시 에러 처리

1.1. await로 즉시 평가하지 않는 코드

const asyncFn = async () => {
  const fn1 = rejected(1).catch((e) => {
    console.error("catch chaining 1", { e });
  });
  console.log("fn1", fn1);
  const fn2 = rejected(2);
  console.log("fn2", fn2);
  const fn3 = rejected(3).catch((e) => {
    console.error("catch chaining 3", { e });
  });
  console.log("fn3", fn3);
};

asyncFn()
  .then(() => console.log("최종 then"))
  .catch(() => console.log("최종 catch"));

  1. reject chaining을 해도 다음코드가 실행된다. 콜백함수는 해당 실행컨텍스트가 완료된 후 실행되기 때문이다.
  2. await로 평가하지 않으면 reject chaining을 해도 상위 실행컨텍스트는 Promise<fulfilled> 되어 then이 실행된다.
  3. reject chaining을 하지 않은 비동기함수에서 에러가 발생하면 Uncaught 에러로 평가되어 에러를 처리할 수 없다.
  4. reject chaining에서 에러를 throw하면 Uncaught 에러로 전달되어 에러를 처리할 수 없다.

1.2. await로 즉시 평가하여 에러처리하는 코드

const asyncFn = async () => {
  const fn1 = await rejected(1).catch((e) => { // await 로 즉시 평가
    console.error("catch chaining 1", { e });
    throw e;
  });
  console.log("fn1", fn1);
  const fn2 = rejected(2);
  console.log("fn2", { fn2 });
  const fn3 = rejected(3).catch((e) => {
    console.error("catch chaining 3", { e });
    throw e;
  });
  console.log("fn3", { fn3 });
};

  1. await로 비동기함수를 평가하면 reject chaining 구문이 우선 실행된다.
  2. reject chaining에서 에러를 throw하면 상위 스코프로 에러가 전달된다.
    • await로 평가한 reject chaining이라면 즉시 해당 실행컨텍스트가 종료된다.
  3. reject chaining에서 에러를 throw하지 않으면 상위 스코프로 에러가 전달되지 않는다.
    • catch 구문에서 에러를 적절히 처리했다고 판단하여 해당 실행컨텍스트가 종료되지 않는다.
    • 실행컨텍스트가 살아있으니 다음 코드가 실행된다.

2. try-catch 구문 사용 시 에러 처리

2.1. await로 즉시 평가하지 않는 코드

const asyncFn = async () => {
  try {
    const fn1 = rejected(1).catch((e) => {
      console.error("catch chaining 1", { e });
      throw e; // catch구문에서 throw하더라도 await로 즉시평가되지 않았기 때문에 try-catch구문에서는 잡을 수 없음.
    });
    console.log("fn1", fn1);
    const fn2 = rejected(2);
    console.log("fn2", { fn2 });
    const fn3 = rejected(3).catch((e) => {
      console.error("catch chaining 3", { e });
      throw e;
    });
    console.log("fn3", { fn3 });
  } catch (e) {
    console.error("try-catch", { e });
    throw e;
  }
};

  1. reject chaining에서 throw 하더라도 await로 평가하지 않았기 때문에 try-catch로 감싼 스코프 내에서는 해당 에러를 처리할 수 없다.
  2. try-catch 스코프 내에서 비동기함수들이 await로 즉시 평가되지 않기 때문에 Promise<fulfilled> 상태로 실행컨텍스트가 종료된다.
  3. try-catch 실행컨텍스트가 종료된 이후 비동기함수에서 에러가 발생하고 catch구문에서 throw처리하더라도 에러를 받을 컨텍스트가 없기 때문에 Uncaught 에러로 종료된다.

2.2. await로 즉시 평가하여 에러처리하는 코드

const asyncFn = async () => {
  try {
    const fn1 = rejected(1).catch((e) => {
      console.error("catch chaining 1", { e });
    });
    console.log("fn1", await fn1); // await 구문으로 try-catch 내에서 평가하지만 throw X
    const fn2 = rejected(2);
    console.log("fn2", fn2);
    const fn3 = rejected(3).catch((e) => {
      console.error("catch chaining 3", { e });
      throw e;
    });
    console.log("fn3", await fn3); // await 구문으로 try-catch 내에서 평가 및 throw
  } catch (e) {
    console.error("try-catch", { e });
    throw e;
  }
};

  1. await 구문으로 비동기함수 내에서 해당 값을 평가한다.
  2. 에러가 발생하면 try-catch 스코프 내에서 처리할 수 있다.
  3. reject chaining을 한 경우 throw하지 않으면 try-catch에서 처리할 수 없다.
  4. reject chaining을 하지 않으면 자동으로 상위스코프로 에러가 throw 된다.

결론

  1. 비동기 함수를 사용할 때 되도록 await 구문을 사용해서 값을 평가할 수 있도록 하자.
  2. Promise chaining을 하지 않으면 알아서 상위스코프로 throw된다.
  3. try-catch를 하지 않으면 알아서 상위스코프로 throw된다.
  4. await를 사용한다는 가정하에 Promise chaining 이나 try-catch 가 없으면 알아서 상위스코프로 throw된다.
  5. Promise chaining을 한 경우 catch 절에서는 상위스코프로 throw해야 에러를 제어할 수 있다.
    • catch 절에서 throw하지 않으면 해당 비동기 함수는 잘 처리된 것으로 간주되어 Promise<fulfilled> 된다.
  6. try-catch를 한 경우 catch절에서 상위스코프로 throw해야 에러를 제어할 수 있다.
    • catch 절에서 throw하지 않으면 해당 비동기 함수는 잘 처리된 것으로 간주되어 Promise<fulfilled> 된다.
profile
FE Developer

0개의 댓글