🛠️ Promise와 async/await 실전 활용법 – 초보자도 쉽게 따라하는 완벽 가이드

개발 에러 해결 가이드 - FixLog 노트

Promise와 async/await 실전 활용법 – 초보자도 쉽게 따라하는 완벽 가이드

1. 도입 – 학습 목표 및 필요성

현대 JavaScript 개발에서 비동기 처리는 필수적인 기술입니다. 이 가이드에서는 Promise와 async/await 실전 활용법을 체계적으로 학습하여, 복잡한 비동기 작업을 효율적으로 처리하는 방법을 익히게 됩니다. API 호출, 파일 읽기, 데이터베이스 쿼리 등 실무에서 자주 마주치는 비동기 상황을 우아하게 해결할 수 있습니다. 콜백 지옥(Callback Hell)에서 벗어나 가독성 높고 유지보수가 쉬운 코드를 작성하는 것이 이 튜토리얼의 핵심 목표입니다. 초보자도 단계별로 따라하면서 실전 감각을 키울 수 있도록 구성했습니다.

2. 기본 개념 설명

Promise란?

Promise는 비동기 작업의 최종 완료 또는 실패를 나타내는 객체입니다. 세 가지 상태를 가집니다:

  • Pending(대기): 초기 상태, 아직 완료되지 않음
  • Fulfilled(이행): 작업이 성공적으로 완료됨
  • Rejected(거부): 작업이 실패함

Promise는 .then(), .catch(), .finally() 메서드를 통해 결과를 처리합니다.

async/await란?

async/await는 ES2017에서 도입된 문법으로, Promise를 더 간결하고 동기 코드처럼 작성할 수 있게 해줍니다. async 키워드는 함수가 항상 Promise를 반환하도록 만들고, await 키워드는 Promise가 처리될 때까지 함수 실행을 일시 중지합니다. 이를 통해 비동기 코드의 가독성이 크게 향상되며, 에러 처리도 try-catch 구문으로 직관적으로 할 수 있습니다. Promise와 async/await 실전 활용법을 마스터하면 복잡한 비동기 로직도 쉽게 구현할 수 있습니다.

3. 단계별 구현 가이드

Step 1: 기본 Promise 생성하기

먼저 간단한 Promise를 만들어봅시다. Promise 생성자는 executor 함수를 인자로 받으며, 이 함수는 resolvereject 두 개의 콜백을 매개변수로 받습니다.

// 기본 Promise 생성
const simplePromise = new Promise((resolve, reject) => {
  const success = true;
  
  setTimeout(() => {
    if (success) {
      resolve('작업이 성공했습니다!');
    } else {
      reject('작업이 실패했습니다.');
    }
  }, 1000);
});

// Promise 사용
simplePromise
  .then(result => console.log(result))
  .catch(error => console.error(error));

Step 2: Promise 체이닝

여러 비동기 작업을 순차적으로 실행하려면 Promise 체이닝을 사용합니다. 각 .then()은 새로운 Promise를 반환하므로 연결할 수 있습니다.

// 사용자 정보 가져오기 시뮬레이션
function getUser(userId) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({ id: userId, name: '홍길동' });
    }, 1000);
  });
}

// 사용자의 주문 정보 가져오기
function getOrders(user) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve([{ orderId: 1, product: '노트북' }, { orderId: 2, product: '마우스' }]);
    }, 1000);
  });
}

// Promise 체이닝으로 순차 실행
getUser(100)
  .then(user => {
    console.log('사용자:', user);
    return getOrders(user);
  })
  .then(orders => {
    console.log('주문 목록:', orders);
  })
  .catch(error => {
    console.error('에러 발생:', error);
  });

Step 3: async/await로 변환하기

위의 Promise 체이닝을 async/await로 변환하면 훨씬 읽기 쉬운 코드가 됩니다.

async function getUserAndOrders(userId) {
  try {
    const user = await getUser(userId);
    console.log('사용자:', user);
    
    const orders = await getOrders(user);
    console.log('주문 목록:', orders);
    
    return orders;
  } catch (error) {
    console.error('에러 발생:', error);
    throw error;
  }
}

// 함수 호출
getUserAndOrders(100);

Step 4: 병렬 처리하기

여러 독립적인 비동기 작업을 동시에 실행하려면 Promise.all()을 사용합니다.

async function fetchMultipleData() {
  try {
    // 세 개의 API를 동시에 호출
    const [users, products, settings] = await Promise.all([
      fetch('/api/users').then(res => res.json()),
      fetch('/api/products').then(res => res.json()),
      fetch('/api/settings').then(res => res.json())
    ]);
    
    console.log('모든 데이터 로드 완료');
    return { users, products, settings };
  } catch (error) {
    console.error('데이터 로드 실패:', error);
  }
}

Step 5: 에러 처리 패턴

실전에서는 견고한 에러 처리가 중요합니다. 여러 패턴을 조합하여 사용할 수 있습니다.

async function robustDataFetch(url) {
  let retries = 3;
  
  while (retries > 0) {
    try {
      const response = await fetch(url);
      
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      
      const data = await response.json();
      return data;
    } catch (error) {
      retries--;
      console.log(`재시도 남은 횟수: ${retries}`);
      
      if (retries === 0) {
        throw new Error(`최종 실패: ${error.message}`);
      }
      
      // 1초 대기 후 재시도
      await new Promise(resolve => setTimeout(resolve, 1000));
    }
  }
}

4. 실제 코드 예제와 설명

실무에서 자주 사용되는 실전 시나리오를 살펴보겠습니다. 다음은 여러 사용자의 데이터를 순차적으로 처리하면서 진행 상황을 추적하는 예제입니다.

// 실전 예제: 사용자 데이터 일괄 처리
class UserDataProcessor {
  constructor() {
    this.processedCount = 0;
  }
  
  async processUser(userId) {
    // API에서 사용자 데이터 가져오기
    const userData = await fetch(`/api/users/${userId}`).then(res => res.json());
    
    // 데이터 검증 및 변환
    const validatedData = this.validateUser(userData);
    
    // 데이터베이스에 저장
    await this.saveToDatabase(validatedData);
    
    this.processedCount++;
    return validatedData;
  }
  
  validateUser(user) {
    if (!user.email || !user.name) {
      throw new Error('필수 정보가 누락되었습니다.');
    }
    return {
      ...user,
      processedAt: new Date().toISOString()
    };
  }
  
  async saveToDatabase(data) {
    // 데이터베이스 저장 시뮬레이션
    return new Promise((resolve) => {
      setTimeout(() => resolve(true), 500);
    });
  }
  
  async processBatch(userIds) {
    const results = [];
    const errors = [];
    
    for (const userId of userIds) {
      try {
        const result = await this.processUser(userId);
        results.push(result);
        console.log(`처리 완료: ${this.processedCount}/${userIds.length}`);
      } catch (error) {
        errors.push({ userId, error: error.message });
        console.error(`사용자 ${userId} 처리 실패:`, error.message);
      }
    }
    
    return { results, errors, total: userIds.length };
  }
}

// 사용 예시
async function main() {
  const processor = new UserDataProcessor();
  const userIds = [1, 2, 3, 4, 5];
  
  const summary = await processor.processBatch(userIds);
  console.log('처리 결과:', summary);
}

main();

이 예제는 Promise와 async/await 실전 활용법의 핵심을 보여줍니다. 에러 처리, 진행 상황 추적, 일괄 처리 등 실무에서 필요한 모든 요소를 포함하고 있습니다.

5. 고급 활용 방법

Promise.race() 활용

타임아웃 구현이나 가장 빠른 응답 선택 시 유용합니다.

async function fetchWithTimeout(url, timeout = 5000) {
  const timeoutPromise = new Promise((_, reject) => {
    setTimeout(() => reject(new Error('요청 시간 초과')), timeout);
  });
  
  const fetchPromise = fetch(url);
  
  return Promise.race([fetchPromise, timeoutPromise]);
}

Promise.allSettled() 사용

모든 Promise의 성공/실패 여부와 관계없이 모든 결과를 받을 때 사용합니다.

async function fetchAllData(urls) {
  const promises = urls.map(url => fetch(url).then(res => res.json()));
  const results = await Promise.allSettled(promises);
  
  const successful = results.filter(r => r.status === 'fulfilled').map(r => r.value);
  const failed = results.filter(r => r.status === 'rejected').map(r => r.reason);
  
  return { successful, failed };
}

async 제너레이터 활용

async function* dataStream(urls) {
  for (const url of urls) {
    const data = await fetch(url).then(res => res.json());
    yield data;
  }
}

// 사용
async function processStream() {
  for await (const data of dataStream(['/api/1', '/api/2', '/api/3'])) {
    console.log('데이터 처리:', data);
  }
}

6. 마무리 및 추가 학습 자료

Promise와 async/await 실전 활용법을 통해 비동기 JavaScript의 핵심을 학습했습니다. 이제 여러분은 복잡한 비동기 작업도 자신있게 처리할 수 있습니다. 더 깊이 있는 학습을 위해 다음 자료를 추천합니다:

  • MDN Web Docs의 Promise 및 async/await 공식 문서
  • JavaScript.info의 비동기 프로그래밍 섹션
  • 실제 프로젝트에서 API 호출 및 데이터 처리 구현 연습
  • Node.js 환경에서의 비동기 파일 처리 학습

지속적인 연습과 실전 적용을 통해 Promise와 async/await 실전 활용법을 완벽히 마스터하시기 바랍니다. 질문이나 피드백이 있다면 커뮤니티에서 공유해주세요!

📚 함께 읽으면 좋은 글

1

JavaScript 비동기 프로그래밍 마스터하기 – 초보자도 쉽게 따라하는 완벽 가이드

📂 JavaScript 튜토리얼
📅 2025. 10. 7.
🎯 JavaScript 비동기 프로그래밍 마스터하기

2

JavaScript 클로저 이해하고 활용하기 – 초보자도 쉽게 따라하는 완벽 가이드

📂 JavaScript 튜토리얼
📅 2025. 10. 6.
🎯 JavaScript 클로저 이해하고 활용하기

3

JavaScript 클로저 이해하고 활용하기 – 초보자도 쉽게 따라하는 완벽 가이드

📂 JavaScript 튜토리얼
📅 2025. 10. 5.
🎯 JavaScript 클로저 이해하고 활용하기

4

ES6 화살표 함수 완벽 가이드 – 초보자도 쉽게 따라하는 완벽 가이드

📂 JavaScript 튜토리얼
📅 2025. 10. 5.
🎯 ES6 화살표 함수 완벽 가이드

5

JavaScript 모듈 시스템 완전 정복 – 초보자도 쉽게 따라하는 완벽 가이드

📂 JavaScript 튜토리얼
📅 2025. 10. 4.
🎯 JavaScript 모듈 시스템 완전 정복

💡 위 글들을 통해 더 깊이 있는 정보를 얻어보세요!

📢 이 글이 도움되셨나요? 공유해주세요!

여러분의 공유 한 번이 더 많은 사람들에게 도움이 됩니다 ✨


📘 페이스북


🐦 트위터


✈️ 텔레그램

🔥 공유할 때마다 블로그 성장에 큰 힘이 됩니다! 감사합니다 🙏

💬 여러분의 소중한 의견을 들려주세요!

여러분은 Promise와 async/await 실전 활용법에 대해 어떻게 생각하시나요?

💡
유용한 정보 공유

궁금한 점 질문

🤝
경험담 나누기

👍
의견 표현하기

⭐ 모든 댓글은 24시간 내에 답변드리며, 여러분의 의견이 다른 독자들에게 큰 도움이 됩니다!
🎯 건설적인 의견과 경험 공유를 환영합니다 ✨

🔔 블로그 구독하고 최신 글을 받아보세요!

📚
다양한 주제
17개 카테고리

정기 업데이트
하루 3회 발행

🎯
실용적 정보
바로 적용 가능

💡
최신 트렌드
2025년 기준

🌟 JavaScript 튜토리얼부터 다양한 실생활 정보까지!
매일 새로운 유용한 콘텐츠를 만나보세요 ✨

📧 RSS 구독 | 🔖 북마크 추가 | 📱 모바일 앱 알림 설정
지금 구독하고 놓치는 정보 없이 업데이트 받아보세요!

📱 전체 버전 보기