모든 강의를 무료로 볼 수 있어요. 회원가입 없이도 학습 가능합니다.

25강 / 전체 30강

async·await

14분 읽기 조회 0

async 함수 선언, await로 Promise 순차 처리, try/catch/finally로 오류 처리, 병렬 처리 패턴, 최상위 await, 실전 활용 패턴까지 async·await를 완전히 익힙니다.

🎯 학습 목표

이 강을 마치면 다음을 할 수 있습니다.

  • async 함수와 await 키워드의 역할을 이해합니다.
  • try/catch/finally로 async 함수에서 오류를 처리할 수 있습니다.
  • await Promise.all()로 병렬 비동기 처리를 구현할 수 있습니다.
  • 24강의 Promise 체이닝과 async/await를 상황에 맞게 선택할 수 있습니다.

⚡ async/await란

이 섹션에서는 async/await가 무엇인지, Promise와 어떤 관계인지 살펴보겠습니다.

async/await는 ES2017(ES8)에서 도입된 문법으로, Promise를 기반으로 동작하지만 동기 코드처럼 읽힐 수 있게 작성하는 방법입니다. .then() 체이닝 없이 마치 위에서 아래로 순서대로 실행되는 것처럼 보이는 코드를 작성할 수 있습니다. 내부적으로는 Promise를 사용하므로, 24강의 Promise를 이해한 것이 기반이 됩니다.

// async 함수 선언
async function fetchUser(id) {
  // await: Promise가 완료될 때까지 기다린 후 결과값 반환
  const response = await fetch('/api/users/' + id);
  const user = await response.json();
  return user;  // async 함수는 항상 Promise를 반환
}

// 일반 함수 vs async 함수 반환값 비교
function normalFn() { return 42; }         // 42 반환
async function asyncFn() { return 42; }    // Promise<42> 반환

console.log(normalFn());   // 42
console.log(asyncFn());    // Promise { 42 }

// async 함수의 결과는 .then()으로 받거나 await로 기다림
asyncFn().then(val => console.log(val));   // 42
const val = await asyncFn();               // 42 (다른 async 함수 안에서)

async 키워드를 함수 앞에 붙이면 그 함수는 항상 Promise를 반환하는 비동기 함수가 됩니다. 함수 안에서 return 42를 하면 Promise.resolve(42)를 반환하는 것과 동일합니다. await는 async 함수 안에서만 사용할 수 있습니다. await 뒤에 Promise를 놓으면, 그 Promise가 완료될 때까지 함수 실행을 일시 중단하고 결과값을 반환합니다. 중요한 점은 await가 함수를 중단하는 동안 이벤트 루프는 차단하지 않습니다. 다른 작업이 계속 실행될 수 있습니다.

🔄 Promise 체이닝 vs async/await 비교

이 섹션에서는 동일한 작업을 두 가지 방식으로 작성해 가독성 차이를 직접 확인해 보겠습니다.

// Promise 체이닝 방식 (24강)
function loadUserData(userId) {
  return fetch('/api/users/' + userId)
    .then(response => {
      if (!response.ok) throw new Error('사용자 조회 실패');
      return response.json();
    })
    .then(user => {
      return fetch('/api/orders?userId=' + user.id)
        .then(res => res.json())
        .then(orders => ({ user, orders }));
    })
    .then(({ user, orders }) => {
      return fetch('/api/products/' + orders[0].productId)
        .then(res => res.json())
        .then(product => ({ user, orders, product }));
    })
    .catch(err => console.error(err));
}

// async/await 방식 (훨씬 읽기 쉬움)
async function loadUserData(userId) {
  const userRes = await fetch('/api/users/' + userId);
  if (!userRes.ok) throw new Error('사용자 조회 실패');
  const user = await userRes.json();

  const ordersRes = await fetch('/api/orders?userId=' + user.id);
  const orders = await ordersRes.json();

  const productRes = await fetch('/api/products/' + orders[0].productId);
  const product = await productRes.json();

  return { user, orders, product };
}

// 호출
loadUserData(1)
  .then(data => console.log(data))
  .catch(err => console.error(err));

async/await 버전은 마치 동기 코드처럼 읽힙니다. 복잡한 .then() 체이닝 없이 위에서 아래로 자연스럽게 흐릅니다. 특히 중간에 조건 분기(if)나 반복(for)이 필요할 때 async/await가 훨씬 유연합니다. 반면 .then() 체이닝은 간단한 변환을 연결할 때 더 간결할 수 있습니다. 둘 다 Promise이므로 혼용도 가능합니다.

🛡️ try/catch/finally로 오류 처리

이 섹션에서는 async 함수에서 오류를 처리하는 try/catch 패턴을 살펴보겠습니다.

24강의 Promise에서 .catch()로 오류를 처리했다면, async/await에서는 일반 동기 코드와 동일한 try/catch/finally를 사용합니다. 이것이 async/await의 큰 장점 중 하나입니다.

async function getUserData(id) {
  try {
    const response = await fetch('/api/users/' + id);

    if (!response.ok) {
      throw new Error('HTTP 오류: ' + response.status);
    }

    const user = await response.json();
    return user;

  } catch (error) {
    // await에서 발생한 오류를 여기서 잡음
    console.error('사용자 조회 실패:', error.message);
    return null;  // 기본값 반환

  } finally {
    // 성공/실패 모두 실행
    console.log('요청 완료');
    hideLoadingSpinner();
  }
}

// 사용
const user = await getUserData(1);
if (user) {
  console.log(user.name);
}

// 오류 유형별 처리
async function processData() {
  try {
    const data = await fetchData();
    const result = await transformData(data);
    return result;
  } catch (error) {
    if (error.name === 'NetworkError') {
      console.error('네트워크 오류 — 재시도 필요');
    } else if (error.name === 'ValidationError') {
      console.error('데이터 형식 오류:', error.message);
    } else {
      throw error;  // 처리 못한 오류는 다시 throw
    }
  }
}

try 블록 안의 await에서 오류가 발생하면 즉시 catch 블록으로 이동합니다. finally는 성공/실패 여부와 관계없이 항상 실행되므로 정리 작업에 적합합니다. catch에서 return null처럼 기본값을 반환하면 호출자에서 null 체크로 처리할 수 있고, throw error로 다시 던지면 상위 호출자로 오류를 전파합니다.

⚡ 병렬 처리 — await + Promise.all

이 섹션에서는 async/await에서 여러 작업을 병렬로 실행하는 방법을 살펴보겠습니다.

async/await의 함정 중 하나는 await를 연속으로 사용하면 의도치 않게 순차 실행이 될 수 있다는 것입니다. 서로 독립적인 작업이라면 병렬로 실행해야 더 빠릅니다.

// 순차 실행 — 불필요하게 느림 (각각 기다림)
async function loadSequential() {
  const user     = await fetchUser(1);    // 1초 기다림
  const posts    = await fetchPosts(1);   // 또 1초 기다림
  const comments = await fetchComments(1); // 또 1초 기다림
  // 총 3초!
  return { user, posts, comments };
}

// 병렬 실행 — Promise.all 활용 (훨씬 빠름)
async function loadParallel() {
  const [user, posts, comments] = await Promise.all([
    fetchUser(1),
    fetchPosts(1),
    fetchComments(1)
  ]);
  // 총 1초 (가장 느린 요청 시간)
  return { user, posts, comments };
}

// 언제 순차, 언제 병렬?
async function loadOrderedData(userId) {
  // 사용자 정보 먼저 필요 (다음 요청에 user.id 사용)
  const user = await fetchUser(userId);   // 순차: user.id가 필요

  // user.id를 알았으면 주문과 알림은 동시에
  const [orders, notifications] = await Promise.all([
    fetchOrders(user.id),         // 병렬: 서로 독립적
    fetchNotifications(user.id)   // 병렬
  ]);

  return { user, orders, notifications };
}

// for await...of — 비동기 반복
async function processItems(ids) {
  for (const id of ids) {
    const item = await fetchItem(id);  // 각 항목 순차 처리
    await saveItem(item);
  }
  // 주의: ids.map(id => fetchItem(id))는 모두 동시에 시작
}

서로 의존성이 없는 비동기 작업은 반드시 Promise.all()로 병렬화하세요. 특히 API 여러 개를 호출해 페이지를 초기화하는 패턴에서 이 차이가 체감됩니다. 순차 실행이 필요한 경우는 이전 결과가 다음 요청의 입력이 될 때입니다. for...of 안에서의 await는 순차 실행이며, forEach에서 await는 동작하지 않습니다 — 반드시 for...of나 for...in을 사용하세요.

🌐 최상위 await (Top-level await)

이 섹션에서는 모듈 최상위 레벨에서 await를 바로 사용하는 최신 기능을 살펴보겠습니다.

ES2022부터 ES 모듈(type="module")에서는 async 함수로 감싸지 않고 최상위 레벨에서 await를 직접 사용할 수 있습니다.

// 기존: async 래퍼 필요
(async () => {
  const config = await loadConfig();
  app.start(config);
})();

// 최상위 await (ES 모듈에서만, type="module")
const config = await loadConfig();
app.start(config);

// 실전: 설정 파일 로딩, 초기화
const response = await fetch('/api/config');
const settings = await response.json();

console.log('앱 시작:', settings.version);

💡 실전 패턴 모음

이 섹션에서는 자주 마주치는 실전 async/await 패턴을 살펴보겠습니다.

// 패턴 1: 재시도 로직
async function fetchWithRetry(url, maxRetries = 3) {
  let lastError;
  for (let i = 0; i < maxRetries; i++) {
    try {
      const res = await fetch(url);
      if (!res.ok) throw new Error('HTTP ' + res.status);
      return await res.json();
    } catch (error) {
      lastError = error;
      console.log('재시도 ' + (i + 1) + '/' + maxRetries);
      await delay(1000 * (i + 1));  // 지수 백오프
    }
  }
  throw lastError;
}

// 패턴 2: 캐싱
const cache = new Map();
async function fetchCached(url) {
  if (cache.has(url)) return cache.get(url);
  const data = await fetch(url).then(r => r.json());
  cache.set(url, data);
  return data;
}

// 패턴 3: 동시 요청 수 제한 (concurrency limit)
async function processWithLimit(items, limit, asyncFn) {
  const results = [];
  for (let i = 0; i < items.length; i += limit) {
    const batch = items.slice(i, i + limit);
    const batchResults = await Promise.all(batch.map(asyncFn));
    results.push(...batchResults);
  }
  return results;
}

// 100개 항목을 한 번에 5개씩 병렬 처리
const items = Array.from({ length: 100 }, (_, i) => i + 1);
const results = await processWithLimit(items, 5, async (id) => {
  return fetch('/api/items/' + id).then(r => r.json());
});

📝 핵심 요약

25강에서 배운 내용을 정리해 보겠습니다.

개념설명
async function항상 Promise를 반환하는 비동기 함수
awaitPromise 완료까지 일시 중단 (async 내부에서만)
try/catchawait 오류를 동기 코드처럼 처리
await Promise.all()독립적 작업 병렬 실행 — 시간 절약
순차 vs 병렬의존성 있으면 순차, 독립적이면 병렬
forEach + await동작 안 함 — for...of를 사용할 것

다음 강인 26강 — fetch로 API 호출에서는 실제 서버와 데이터를 주고받는 방법을 배웁니다. fetch API의 GET·POST 요청, 요청 헤더, JSON 전송, 응답 처리까지 — 25강의 async/await와 결합하면 실제 서버와 통신하는 완전한 웹 앱을 만들 수 있습니다!

관련 주제

  • async await
  • try catch finally
  • await Promise.all
  • 병렬 비동기 처리
  • 최상위 await
  • 개발·프로그래밍
  • 개발·프로그래밍 강의
  • 자바스크립트 기초 30강 — 웹을 움직이는 언어
  • 무료강의
  • 무료 온라인 강의
  • NUGUNA
  • 누구나

📚 시리즈 전체 공유

자바스크립트 기초 30강 — 웹을 움직이는 언어

이 강의가 속한 시리즈는 총 30강, 모두 무료입니다. 처음부터 배우려는 동료에게 시리즈 전체를 알려 주세요.

댓글

0/1000

불러오는 중...