로그인 입점하기
← 가이드 목록
시작하기

async/await란? JavaScript 비동기 코드를 쉽게 읽고 작성하는 방법

async/await는 Promise 기반 비동기 코드를 위에서 아래로 읽히게 쓰는 JavaScript 문법입니다. async·await·try/catch·Promise.all·순차/동시·forEach 함정·Abort·Long Task까지 초보 기준으로 정리합니다.

async/await란? JavaScript 비동기 코드를 쉽게 읽고 작성하는 방법
순차 await 폭포와 Promise.all 동시 실행을 비교한 흐름도
async/await는 Promise를 대체하는 별도 시스템이 아니라, 위에서 아래로 읽히게 쓰는 문법입니다.

이전 글에서 Promise를 살펴봤습니다. .then()이 여러 단계로 이어지면 읽기 어려워질 수 있습니다. async/await는 Promise 기반 비동기 코드를 일반적인 JavaScript처럼 위에서 아래로 읽고 작성하게 해 주는 문법입니다.

async · await란

async는 함수 앞에 붙이는 키워드입니다. async 함수는 항상 Promise를 반환합니다. 숫자 10을 return해도 개념적으로는 Promise.resolve(10)과 비슷합니다. await는 Promise 결과가 준비될 때까지 그 async 함수의 다음 진행만 잠시 멈춥니다. JavaScript 전체를 멈추는 것이 아닙니다. 식당 직원이 주문을 주방에 넘긴 뒤 다른 손님을 응대하는 것과 같습니다.

async function load() {
  const user = await login();
  const profile = await loadProfile(user);
  const orders = await loadOrders(profile);
  console.log(orders);
}

일반적으로 await는 async 함수 안에서 씁니다. 일반 함수에만 쓰면 문법 오류가 날 수 있습니다. Top-level await는 모듈·환경 지원이 필요하므로 기본은 「await는 async와 함께」로 이해하면 됩니다.

오류 · fetch · finally

await 중인 Promise가 Rejected되면 그 지점에서 오류처럼 처리됩니다. try/catch·finally로 다루면 Promise의 .catch·.finally와 비슷한 목적을 달성할 수 있습니다. 로딩 표시는 finally에서 끄는 패턴이 흔합니다. fetch는 Response와 response.json() 둘 다 Promise라 await를 두 번 쓰는 경우가 많습니다. HTTP 404·500이어도 fetch가 항상 throw하지는 않으므로 response.ok를 확인합니다 — HTTP 오류와 Promise Rejection은 다른 축입니다. 복구 가능한 오류(추천 목록 빈 배열)와 반드시 중단해야 할 오류(결제 실패)를 구분하세요. 세부 오류는 에러 트래킹에 남기는 편이 좋습니다.

async function loadProducts() {
  showLoading();
  try {
    const response = await fetch("/api/products");
    if (!response.ok) throw new Error("HTTP " + response.status);
    const products = await response.json();
    showProducts(products);
  } catch (error) {
    showError(error);
  } finally {
    hideLoading();
  }
}

순차 · 동시 · 반복 · 취소

const a = await taskA(); const b = await taskB();는 보통 A가 끝난 뒤 B가 시작됩니다. async/await를 썼다고 자동으로 병렬이 되지 않습니다. 서로 의존하면 순차가 맞고, 독립이면 먼저 Promise를 시작하거나 await Promise.all([...])을 검토합니다. 반대로 수만 개를 한꺼번에 Promise.all하면 부하가 커질 수 있어 동시 실행 개수 제한이 필요할 수 있습니다. forEach(async () => { await ... })는 forEach가 내부 Promise 완료를 기다리지 않습니다. 순차라면 for...of + await, 동시라면 Promise.all(items.map(...))을 씁니다. Timeout·Abort는 자동이 아닙니다. 검색처럼 빠른 연속 요청은 AbortController나 최신 요청 ID로 Race Condition을 막습니다.

// 독립 요청 → 동시
const [products, notices] = await Promise.all([
  loadProducts(),
  loadNotices()
]);

// 순차 저장
for (const item of items) {
  await saveItem(item);
}

이벤트 루프 · CPU · 흔한 실수

await 이후 코드는 Promise 완료 뒤 Microtask·이벤트 루프를 거쳐 재개됩니다. 이미 Fulfilled인 Promise라도 await 뒤는 동기 실행 중간에 바로 끼어들지 않습니다. 네트워크 I/O를 await하는 동안 메인 스레드는 다른 일을 할 수 있지만, 동기 무거운 계산을 await한다고 Long Task가 사라지지는 않습니다. Promise.resolve·setTimeout(0)만으로 CPU가 백그라운드가 되지 않습니다. Node.js도 같은 원리입니다 — I/O await 중에는 다른 요청을 처리할 수 있어도, 긴 동기 계산은 이벤트 루프를 막습니다.

흔한 실수: 독립 작업을 순차 await(Request Waterfall) · await가 CPU를 비동기로 만든다고 믿기 · try/catch 생략 · HTTP 오류를 자동 catch로 기대 · forEach가 async를 기다릴 것이라 기대 · 무제한 Promise.all · await만으로 화면이 부드러워진다고 기대. Network 패널에서 Waterfall을 확인하면 불필요 순차 요청을 찾기 쉽습니다.

자주 묻는 질문

async/await란?

Promise 기반 비동기 코드를 위에서 아래로 읽는 일반적인 코드처럼 작성할 수 있게 돕는 JavaScript 문법입니다.

await하면 JS 전체가 멈추나요?

아닙니다. 해당 async 함수만 기다리고, 이벤트 루프는 다른 작업을 처리할 수 있습니다.

Promise와 다른 기술인가요?

아닙니다. Promise 위에서 동작하는 읽기 쉬운 문법입니다.

차례로 await하면 동시에 실행되나요?

아닙니다. 앞 await가 끝난 뒤 다음이 시작됩니다. 독립이면 Promise.all을 검토하세요.

forEach에서 await하면?

일반적인 forEach(async…)는 내부 Promise 완료를 기다리지 않습니다. for…of 또는 Promise.all을 쓰세요.

사이트가 빨라지나요?

문법 자체가 성능을 자동 개선하지는 않습니다. 순서·동시성·실제 JS 작업량이 중요합니다.

정리

async = 이 함수는 Promise를 반환합니다. await = 이 Promise 결과가 준비되면 이 함수의 다음 부분을 이어서 실행합니다. await가 JS 전체를 멈추지도, 무거운 계산을 자동 백그라운드로 보내지도 않습니다. 독립 작업을 불필요하게 순차 await하면 Waterfall이 생깁니다. 순차·동시·오류·취소·Timeout·동시성 제한을 함께 봐야 합니다. 여러 작업을 같은 시간대에 진행하면서 「동시에 몇 개까지」를 관리하는 개념이 동시성(Concurrency)입니다.