eatthefrog
비동기 프로그래밍 입문: async, Promise, await 쉽게 이해하기 본문

웹이나 앱을 개발하다 보면 "지금 당장은 결과를 알 수 없지만, 나중에 알 수 있는" 작업들이 많이 있습니다. 서버에서 데이터를 가져오거나, 파일을 읽거나, 사용자의 응답을 기다리는 것들이죠. 이런 작어을 처리하는 방법이 바로 비동기 프로그래밍입니다.
본문에서는 JS/TS에서 비동기 작업을 다루는 핵심 키워드 세 가지를 알아보겠습니다.
핵심 개념
1. async: " 이 함수는 나중에 결과를 줄게요!" 라고 선언
2. Prmoise: 미래에 올 값을 담는 상자
3. await: 상자가 열릴 때까지 기다렸다가, 안의 실제 값을 꺼냄
1. async 함수란?
async는 함수 앞에 붙이는 키워드로, "이 함수는 비동기 작업을 할 거예요"라고 선언하는 것입니다.
핵심 특징
async 함수는 항상 Promise를 반환합니다.
무슨 말이냐고요? 일반 함수는 결과를 바로 돌려주지만, async 함수는 "나중에 줄게요"라는 약속(Promise)을 담아서 돌려줍니다.
// 일반 함수
function sayHello() {
return '안녕'; // 바로 '안녕' 반환
}
// async 함수
async function sayHelloLater() {
return '안녕'; // Promise로 감싸진 '안녕' 반환
}
반환 타입은 자동으로 결정됩니다
- 아무것도 반환하지 않으면 → Promise<void>
- 문자열을 반환하면 → Promise<string>
- 숫자를 반환하면 → Promise<number>
2. Promise: 미래의 값을 담는 상자
Promise를 가장 쉽게 이해하는 방법은 "택배 송장"에 비유하는 것입니다.
택배 송장으로 이해하기
온라인 쇼핑을 하면 물건이 바로 오지 않죠? 대신 송장 번호를 받습니다. 이 송장이 바로 Promise입니다.
- 지금은 물건이 없지만, 나중에 배송이 완료되면 물건을 받을 수 있어요
- 송장(Promise) 안에는 어떤 물건(타입)이 올지 정보가 담겨 있어요
- 배송이 완료되면 실제 물건(값)을 받을 수 있어요
// 서버에서 사용자 정보를 가져오는 함수
// 지금 당장은 없지만, 나중에 문자열이 올 거예요
async function getUserName() {
// ... 서버 통신 로직
return 'kyoka'; // Promise<string> 형태로 반환됨
}
// 숫자를 계산하는 비동기 함수
async function calculateTotal() {
// ... 복잡한 계산
return 29; // Promise<number> 형태로 반환됨
}
Promise는 다양한 타입을 담을 수 있습니다:
- Promise<string> - 나중에 문자열이 올 거예요
- Promise<number> - 나중에 숫자가 올 거예요
- Promise<boolean> - 나중에 참/거짓이 올 거예요
- Promise<string | null> - 나중에 문자열이 오거나 없을 수도 있어요
3. await: 택배가 도착할 때까지 기다리기
택배 송장(Promise)을 받았다면, 실제 물건이 도착할 때까지 기다려야겠죠? 그게 바로 await입니다.
await의 역할
"여기서 잠깐 멈추고, Promise 안의 실제 값이 준비될 때까지 기다릴게요"
async function showUserInfo() {
// getUserName()은 Promise<string>을 반환
// await을 붙이면 실제 string 값을 얻을 때까지 기다림
const name = await getUserName(); // Kyoka'
console.log(name); // 이제 실제 값을 사용할 수 있어요!
}
중요한 규칙들
- await은 async 함수 안에서만 사용 가능
- async가 없는 일반 함수에서는 사용할 수 없어요
- await 뒤에는 Promise를 반환하는 것만 올 수 있어요
- 다른 async 함수 호출
- Promise 객체
- Promise를 반환하는 라이브러리 함수
// ✅ 올바른 사용
await fetchDataFromServer(); // async 함수
await AsyncStorage.getItem('key'); // Promise 반환 함수
// ❌ 잘못된 사용
await 123; // 숫자는 Promise가 아니에요
await '안녕'; // 문자열도 Promise가 아니에요
그렇다면 언제 async/await를 사용해야 할까?
지금까지 async/await가 무엇인지 알아봤습니다. 그렇다면 실제로 언제 사용하면 좋을까요?
한 문장으로 요약하면: async/await는 복잡한 비동기 처리를 마치 일반 코드처럼 읽기 쉽게 만들어주는 도구입니다.
비동기 작업은 "시간이 걸리는 작업"이에요. 서버 통신, 파일 읽기, 데이터베이스 조회 등은 무조건 비동기 로 처리돼야 합니다. 우리가 선택할 수 있는 게 아니에요. , fetchUser(),,,,etc
이런 상황에 사용하세요
1. 작업을 순서대로 진행해야 할 때
: 만약 async/await 없이 구현한다면? 콜백 지옥이나 복잡한 Promise 체이닝으로 코드가 매우 읽기 어려워집니다.
// 예: 회원가입 프로세스
async function signUp(email, password) {
// 1단계: 이메일 중복 확인 (완료되어야 다음으로)
const isAvailable = await checkEmailAvailable(email);
// 2단계: 계정 생성 (1단계 완료 후)
const user = await createAccount(email, password);
// 3단계: 환영 이메일 발송 (2단계 완료 후)
await sendWelcomeEmail(user);
return user;
}
2. 서버 통신이나 데이터 로딩이 필요할 때
: fetch 같은 함수들은 Promise를 반환하기 때문에 await과 찰떡궁합입니다
async function loadUserProfile() {
// 서버에서 데이터 가져오기
const profile = await fetch('/api/user/profile');
const data = await profile.json();
return data;
}
3. 코드의 가독성을 높이고 싶을 때
: 비동기 코드를 일반 코드처럼 위에서 아래로 읽을 수 있어서, 다른 개발자가 봐도 이해하기 쉽습니다.
⚠️ 주의: async/await를 너무 남용하면 안 돼요
async/await는 편리하지만, 무분별하게 사용하면 오히려 앱이 느려질 수 있습니다@.@

문제 상황: 불필요하게 기다리기
// ❌ 나쁜 예: 서로 독립적인 작업인데 순차 실행
async function loadDashboard() {
const user = await fetchUser(); // 1초 대기
const notifications = await fetchNotifications(); // 1초 대기
const settings = await fetchSettings(); // 1초 대기
// 총 3초 소요 😱
}
문제점: 사용자 정보, 알림, 설정은 서로 관련이 없는데도 순서대로 기다리고 있어요. 마치 식당에서 음료, 메인 요리, 디저트를 따로따로 주문하고 하나씩 먹는 것과 같죠.
해결 방법: 병렬 처리
: Promise.all을 사용하면 여러 작업을 동시에 실행할 수 있습니다. 식당에서 한 번에 주문하고 준비되는 대로 받는 것과 같아요.
// ✅ 좋은 예: 동시에 실행
async function loadDashboard() {
// 세 작업을 동시에 시작!
const [user, notifications, settings] = await Promise.all([
fetchUser(),
fetchNotifications(),
fetchSettings()
]);
// 총 1초 소요 (가장 오래 걸리는 작업 기준) 🚀
}
실제로 발생할 수 있는 문제들
- 성능 저하: 동시에 처리해도 되는 작업을 순차 처리하면 사용자 대기 시간 증가
- 메모리 낭비: 불필요한 대기 시간 동안 함수가 멈춰있으면서 메모리 점유
- 사용자 경험 악화: 로딩이 길어져서 답답함
언제 순차, 언제 병렬?
순차 실행 (await 각각 사용)
- 다음 작업이 이전 결과에 의존할 때
- 예: 로그인 → 프로필 로드 → 대시보드 표시
병렬 실행 (Promise.all 사용)
- 서로 독립적인 작업들
- 예: 사용자 정보, 알림 목록, 설정 동시 로드
'프론트엔드 노트' 카테고리의 다른 글
| React-Native - Firebase Analytics 도입하기 (0) | 2025.11.19 |
|---|---|
| 리엑트 1000줄 리팩토링 하기 (0) | 2025.11.13 |
| FCM(Firebase Clouding Messageing) (0) | 2025.11.12 |
| JavaScript: ?? vs || 연산자 비교 (0) | 2025.11.11 |
| 리엑트 라우팅 시스템 (1) | 2025.09.01 |