JavaScript Async/Await 완벽 가이드

들어가며

JavaScript에서 비동기 프로그래밍은 필수입니다. API 호출, 파일 읽기, 타이머 등 많은 작업이 비동기로 처리됩니다. 이 글에서는 async/await를 중심으로 비동기 프로그래밍을 완벽하게 이해해보겠습니다.

JavaScript Async/Await 흐름
그림 1: async/await의 동작 흐름

비동기 프로그래밍이란?

비동기 프로그래밍은 특정 작업이 완료될 때까지 기다리지 않고 다음 작업을 수행하는 방식입니다.

동기 vs 비동기

방식특징사용 사례
동기순차적 실행, 블로킹계산 작업, 파일 처리
비동기병렬 실행, 논블로킹API 호출, I/O 작업

Promise 이해하기

async/await를 이해하려면 먼저 Promise를 알아야 합니다.

Promise 상태 다이어그램
그림 2: Promise의 세 가지 상태

Promise 생성

const myPromise = new Promise((resolve, reject) => {
  const success = true;
  
  if (success) {
    resolve("성공!");
  } else {
    reject("실패!");
  }
});

Promise 사용

myPromise
  .then(result => console.log(result))
  .catch(error => console.error(error))
  .finally(() => console.log("완료"));

async/await 기본

async/await는 Promise를 더 쉽게 사용할 수 있게 해주는 문법입니다.

async 함수 선언

async function fetchUserData() {
  return "사용자 데이터";
}

async 키워드를 붙이면 함수는 자동으로 Promise를 반환합니다.

await 사용

async function getData() {
  const response = await fetch("/api/data");
  const data = await response.json();
  return data;
}
await는 반드시 async 함수 내에서만 사용할 수 있습니다.

이벤트 루프와 비동기

JavaScript의 비동기 처리를 이해하려면 이벤트 루프를 알아야 합니다.

JavaScript 이벤트 루프
그림 3: JavaScript 이벤트 루프 구조

이벤트 루프 구성요소

  1. Call Stack: 실행 중인 함수들이 쌓이는 곳
  2. Web APIs: 브라우저가 제공하는 API
  3. Callback Queue: 실행 대기 중인 콜백 함수들
  4. Event Loop: 스택이 비면 큐에서 콜백을 가져옴

에러 처리

try-catch 사용

async function fetchData() {
  try {
    const response = await fetch("/api/data");
    
    if (!response.ok) {
      throw new Error("HTTP error");
    }
    
    const data = await response.json();
    return data;
  } catch (error) {
    console.error("에러 발생:", error.message);
    throw error;
  }
}

여러 Promise 에러 처리

async function fetchMultiple() {
  try {
    const [users, posts] = await Promise.all([
      fetch("/api/users").then(r => r.json()),
      fetch("/api/posts").then(r => r.json())
    ]);
    return { users, posts };
  } catch (error) {
    console.error("하나 이상의 요청 실패:", error);
  }
}

병렬 처리

Promise.all

여러 비동기 작업을 동시에 실행합니다:

async function fetchAllData() {
  const [user, posts, comments] = await Promise.all([
    fetchUser(1),
    fetchPosts(1),
    fetchComments(1)
  ]);
  
  return { user, posts, comments };
}

Promise.race

가장 먼저 완료되는 Promise의 결과를 반환합니다:

async function fetchWithTimeout(url, timeout) {
  return Promise.race([
    fetch(url),
    new Promise((_, reject) => 
      setTimeout(() => reject(new Error("Timeout")), timeout)
    )
  ]);
}

Promise.allSettled

모든 Promise가 완료될 때까지 기다립니다:

async function fetchAll(urls) {
  const results = await Promise.allSettled(
    urls.map(url => fetch(url))
  );
  
  results.forEach((result, index) => {
    if (result.status === "fulfilled") {
      console.log(`${urls[index]}: 성공`);
    } else {
      console.log(`${urls[index]}: 실패`);
    }
  });
}

실전 예제

API 데이터 가져오기

async function getUserWithPosts(userId) {
  const userResponse = await fetch(`/api/users/${userId}`);
  const user = await userResponse.json();
  
  const postsResponse = await fetch(`/api/users/${userId}/posts`);
  const posts = await postsResponse.json();
  
  return {
    ...user,
    posts
  };
}

순차적 API 호출

async function processItems(items) {
  const results = [];
  
  for (const item of items) {
    const result = await processItem(item);
    results.push(result);
  }
  
  return results;
}

재시도 로직

async function fetchWithRetry(url, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      const response = await fetch(url);
      return await response.json();
    } catch (error) {
      if (i === maxRetries - 1) throw error;
      await new Promise(r => setTimeout(r, 1000 * (i + 1)));
    }
  }
}
재시도 간격을 점진적으로 늘리는 방식을 지수 백오프(Exponential Backoff)라고 합니다.

흔한 실수들

1. await 없이 Promise 사용

async function wrong() {
  const data = fetch("/api/data");
  console.log(data);
}

async function correct() {
  const response = await fetch("/api/data");
  const data = await response.json();
  console.log(data);
}

2. 불필요한 순차 실행

async function slow() {
  const a = await fetchA();
  const b = await fetchB();
  return [a, b];
}

async function fast() {
  const [a, b] = await Promise.all([
    fetchA(),
    fetchB()
  ]);
  return [a, b];
}

마치며

async/await는 JavaScript 비동기 프로그래밍을 훨씬 직관적으로 만들어줍니다. Promise를 기반으로 하지만 동기 코드처럼 읽기 쉬운 코드를 작성할 수 있습니다. 에러 처리와 병렬 처리 패턴을 잘 활용하여 효율적인 비동기 코드를 작성해보세요.

참고 자료

  • MDN Web Docs - async function
  • JavaScript.info - Async/Await
카테고리: 웹 개발
마지막 수정: 2026년 01월 07일