본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 05. 25. 21:59

2026년에도 여전히 프로덕션 코드를 괴롭히는 3가지 Async/Await 실수

요약

JavaScript의 async/await 사용 시 프로덕션 환경에서 빈번하게 발생하는 세 가지 주요 실수와 해결책을 다룹니다. 누락된 await, 불필요한 순차적 실행, 패턴 혼용 문제를 지적하며 안정적인 비동기 코드 작성법을 제안합니다.

핵심 포인트

  • 누락된 await 방지를 위해 linter 설정 필수
  • 독립적인 작업은 Promise.all 또는 allSettled로 병렬 처리
  • async/await와 .then()/.catch() 패턴의 혼용 지양
  • 빈 catch 블록은 에러를 숨기므로 주의 필요

2026년에도 여전히 프로덕션 코드를 괴롭히는 3가지 Async/Await 실수

지난주에 저는 "이상한" 버그를 디버깅하는 데 2시간을 보냈습니다. 함수는 undefined를 반환하고 있었고, API는 타임아웃이 발생했으며, 에러 메시지는 완전히 잘못된 정보를 제공하고 있었습니다.

근본 원인은 무엇이었을까요? 바로 단 하나의 누락된 await였습니다.

Async/await는 JavaScript에 도입된 지 수년이 지났습니다. 비동기 (async) 코드를 동기 (synchronous) 코드처럼 보이게 하여 깔끔하고 읽기 쉽게 만드는 것이 목적입니다. 하지만 실제로 저는 숙련된 개발자들조차 똑같은 실수를 반복하는 것을 계속해서 목격합니다.

2026년 현재까지도 프로덕션 환경을 망가뜨리는 3가지 async/await 패턴을 공유하겠습니다.

실수 #1: 잊어버린 Await

이것은 전형적인 실수입니다. 수 시간을 낭비하게 만드는 주범이죠.

// 버그: fetchData는 데이터가 아닌 Promise를 반환함
async function getUser() {
  const data = fetchData('/api/user');
...

발생 원인: fetchData가 데이터를 반환하는 것처럼 보이기 때문에 단순한 케이스에서는 함수가 잘 작동합니다. 하지만 속성에 접근하려고 할 때 비로소 하위 단계에서 문제가 발생합니다. TypeScript는 이를 잡아내지만, JS 프로젝트라면? 행운을 빕니다.

전문가 팁: 만약 사용 중인 린터 (linter)가 처리되지 않은 프로미스 (unhandled promises)를 표시하지 않는다면, 당신은 눈을 감고 비행하는 것과 같습니다. 지금 바로 설정에 @typescript-eslint/no-floating-promises를 추가하세요.

실수 #2: 병렬 처리가 필요한 상황에서 순차적 Await 사용

// 직렬로 실행됨: 총 약 3초 소요
async function loadDashboard() {
  const users = await fetchUsers();     // ~1초
...

발생 원인: await를 연속해서 세 번 작성하면 그것이 자연스럽게 느껴집니다. 각 줄은 이전 줄이 완료될 때까지 기다립니다. 하지만 이들은 독립적인 API 호출입니다. 서로를 기다릴 필요가 없습니다.

주의사항: Promise.all은 실패가 빠르게 발생합니다(fails fast). 즉, 하나의 프로미스가 거절(reject)되면 모든 결과를 잃게 됩니다. 부분적인 성공이 필요하다면 Promise.allSettled()를 사용하세요.

실수 #3: Async/Await와 .then()/.catch()의 혼용

// 이것은 엉망진창입니다
async function processOrder(orderId) {
  const order = await getOrder(orderId)
...

이런 현상이 발생하는 이유: 개발자들이 Promise 체인 (Promise chains)에서 전환 중이거나, .catch()가 더 "깔끔하다"고 생각하여 패턴을 혼용하기 때문입니다. 그 결과는 무엇일까요? 에러 처리 (Error handling)가 예측 불가능해집니다.

해결책: 한 가지 스타일을 선택하고 그것을 고수하세요. try/catch를 사용한 Async/await:

// 깔끔하고 예측 가능함
async function processOrder(orderId) {
  try {
...

보너스: 에러 삼키기 (The Error Swallow)

// 버그가 숨어드는 방식
async function updateUser(id, data) {
  try {
...

비어 있는 catch 블록은 시한폭탄과 같습니다. 만약 정말로 에러를 무시하고 싶다면, 그런지 문서화하세요:

// 의도적임: 분석(analytics) 실패가 결제를 막아서는 안 됨
try {
  await trackEvent('purchase_complete', { orderId });
...

결론

Async/await는 JavaScript의 비동기 코드 (async code)를 무한히 읽기 쉽게 만들었습니다. 하지만 가독성 (readability)이 자동으로 정확성 (correctness)을 의미하지는 않습니다.

다음 PR 리뷰를 진행하기 전에 스스로에게 물어보세요:

  1. 모든 Promise가 처리되었는가 (await 되었거나 catch 되었는가)?
  2. 독립적인 작업들이 병렬 (parallel)로 실행되고 있는가?
  3. 함수 전체에서 에러 처리 (error handling)가 일관적인가?

이 세 가지 질문이 있었다면 지난주에 허비한 2시간을 아낄 수 있었을 것입니다.

어떤 비동기 버그가 여러분의 시간을 가장 많이 낭비했나요? 댓글로 공유해 주세요.

Codcompass에서 더 많은 개발자 가이드를 읽어보세요

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
1

댓글

0