게시글

5만명이 선택한 평균 별점 4.9의 제로초 프로그래밍 강좌! 로드맵만 따라오면 됩니다! 클릭
JavaScript - 한 달 전 등록

async 함수 내부에서 에러가 throw된 경우 catch 방법

unhandled promise의 무서움

오늘은 unhandled promise error를 내면 안 되는 이슈를 알아보며 async/await과 error의 특징에 대해 살펴보겠습니다.

async function sub() {
  throw 'hello';
}

async function main() {
  await sub();
}
main();

브라우저: Uncaught (in promise) hello

노드: [UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "hello".] {

  code: 'ERR_UNHANDLED_REJECTION'

}

노드의 에러 메시지가 더 길지만 둘 다 결국은 Promise에 catch를 달지 않았다는 에러(unhandled promise)입니다.

다음과 같이 try/catch문을 쓰면 콘솔에 caught hello라고 뜨며 에러를 잡을 수 있습니다.

async function sub() {
  try {
    throw 'hello';
  } catch (err) {
    console.log('caught', err);
  }
}

async function main() {
  await sub();
}
main();

try/catch문을 main에 써도 될까요?

async function sub() {
  throw 'hello';
}

async function main() {
  try {
    await sub();
  } catch (err) {
    console.log('caught', err);
  }
}
main();

네 이번에도 제대로 잡히는 것을 볼 수 있습니다.

Promise에서 throw된 것도 상위 함수로 전파되며 상위에서 try/catch해도 됩니다.

그러면 다음과 같이 최상위 스코프에서 잡을 수 있을까요?

async function sub() {
  throw 'hello';
}

async function main() {
  await sub();
}
try {
  main();
} catch (err) {
  console.log('caught', err);
}

다시 uncaught (in promise) hello 에러가 뜨게 됩니다. 이 에러는 사실 다음 코드에서도 발생합니다.

async function sub() {
  throw 'hello';
}

async function main() {
  try {
    sub();
  } catch (err) {
    console.log('caught', err);
  }
}
main();

아까는 에러가 잡혔던 것 같은데 무엇이 달라진 걸까요? 바로 sub() 앞에 await이 빠졌다는 것입니다. await이 빠진 경우 try/catch문으로는 잡을 수 없게 됩니다.

이렇게 main 앞에도 await을 붙이면 에러가 잡힙니다. async 함수 없이 main 앞에 await을 붙이는 건 ESM에서 가능한 top level await 문법입니다(노드 CJS에서는 안 됩니다)

async function sub() {
 throw 'hello';
}

async function main() {
 await sub();
}
try {
 await main();
} catch (err) {
 console.log('caught', err);
}

Unhandled promise 에러가 무서운 이유는 한 번 터지면 상위에서도 잡히지 않는다는 점 때문입니다.

async function sub() {
  throw 'hello';
}

async function main() {
  try {
    sub();
  } catch (err) {
    console.log('caught', err);
  }
}
try {
  await main();
  console.log('done');
} catch (err) {
  console.log('caught', err);
}

실행해보면 done이 콘솔에 찍히고 그 다음에 unhandled promise 에러가 발생합니다.

main 실행 시 sub();에서 unhandled promise 에러가 발생했지만 try/catch문에서 잡히지 않는 것입니다.

따라서 await을 반드시 붙여주어야 하고, await을 붙이지 않은 경우는 적어도 .catch() 메서드를 붙여주어야 합니다.

async function sub() {
  throw 'hello';
}

async function main() {  
  sub().catch((err) => console.log('caught', err));
}
main();

이 방법은 지저분하니 await을 꼭 붙여줍시다.

return이 있을 때도 마찬가지입니다.

async function sub() {
  throw 'hello';
}

function main() {
  try {
    return sub();
  } catch (err) {
    console.log('caught', err);
  }
}
main();

위와 같이 하면 에러가 잡히지 않으니 다음과 같이 async/await을 도입해야 합니다. Nest.js를 쓸 때 많이 실수하던 부분이었습니다.

async function sub() {
  throw 'hello';
}

async function main() {
  try {
    return await sub();
  } catch (err) {
    console.log('caught', err);
  }
}
main();

return 뒤에 await을 붙이면 됩니다.

콜백 함수가 async인 경우도 살펴봅시다.

async function sub(cb) {
  await cb();
}

async function main() {
  await sub(async function cb() {
    throw 'callback';
  });
}
try {
  main();
  console.log('done');
} catch (err) {
  console.log('caught', err);
}

실행하면 역시나 done이 출력된 후에 unhandled promise 에러가 발생합니다.

이제 아시겠지만 unhandled promise 에러는 try/catch문에서 잡히지 않습니다.

async function sub(cb) {
  await cb();
}

async function main() {
  await sub(async function cb() {
  throw 'callback';
  });
}
try {
  await main();
} catch (err) {
  console.log('caught', err);
}

해결책도 아시겠죠? main 앞에 await을 붙이면 됩니다. 또는 중간 어디에서든 await을 쓰는 곳에서 try / catch로 잡으면 됩니다.

async function sub(cb) {
  try {
    await cb();
  } catch (err) {
    console.log('caught', err);
  }
}

async function main() {
  await sub(async function cb() {
    throw 'callback';
  });
}

await main();
async function sub(cb) {
  await cb();
}

async function main() {
  try {
    await sub(async function cb() {
      throw 'callback';
    });
  } catch (err) {
    console.log('caught', err);
  }
}

await main();

이번 글을 요약하면 다음과 같습니다.

  • await의 에러는 상위 함수로 전파되며 try/catch문으로 잡을 수 있다
  • await을 붙이지 않고 프로미스에서 에러가 발생하면 unhandled promise 에러가 발생한다.
  • unhandled promise 에러는 try/catch문에서 잡히지 않으니 주의하자
  • await을 붙이지 않은 프로미스는 try/catch문으로 에러를 잡을 수 없다. await이나 catch()를 꼭 붙여야 한다.
조회수:
0
목록
투표로 게시글에 관해 피드백을 해주시면 게시글 수정 시 반영됩니다. 오류가 있다면 어떤 부분에 오류가 있는지도 알려주세요! 잘못된 정보가 퍼져나가지 않도록 도와주세요.
Copyright 2016- . 무단 전재 및 재배포 금지. 출처 표기 시 인용 가능.
5만명이 선택한 평균 별점 4.9의 제로초 프로그래밍 강좌! 로드맵만 따라오면 됩니다! 클릭

댓글

아직 댓글이 없습니다.