Promise vs Async,Await
작성일 : 2024.12.06
1. Promise의 특징
장점:
- 체이닝
.then()을 통한 연속적인 비동기 처리 가능 - 병렬 처리
Promise.all가 직관적 - 에러 처리를 catch로 한번에 가능
// Promise 체이닝 예시
fetchUser()
.then(user => fetchProfile(user.id))
.then(profile => fetchPosts(profile.id))
.catch(error => console.error(error));
// 병렬 처리 예시
Promise.all([
fetchUsers(),
fetchProducts(),
fetchOrders()
]).then(([users, products, orders]) => {
// 모든 데이터 처리
});
// 1. Promise 상태
const promise = new Promise((resolve, reject) => {
// pending: 초기 상태
// fulfilled: resolve 호출
// rejected: reject 호출
});
// 2. Promise 메서드
Promise.resolve(value); // 즉시 이행
Promise.reject(error); // 즉시 거부
Promise.race([p1, p2]); // 가장 빨리 완료된 결과
Promise.allSettled([p1, p2]); // 모든 결과(성공/실패 모두)
// 3. 에러 처리 체이닝
fetchData()
.then(data => {
throw new Error('에러 발생');
})
.then(
success => console.log('실행되지 않음'),
error => console.log('첫 번째 에러 핸들러:', error)
)
.catch(error => console.log('두 번째 에러 핸들러:', error))
.finally(() => console.log('항상 실행'));
단점:
- 코드가 복잡해질수록 then 체이닝이 깊어짐
- 조건부 로직 처리가 복잡
- 디버깅이 어려움
2. async/await의 특징
장점:
- 동기 코드와 유사한 직관적인 문법
- 코드 가독성이 높음
- 디버깅이 용이
- try-catch로 일반적인 에러 처리 가능
// async/await 예시
async function getData() {
try {
const user = await fetchUser();
const profile = await fetchProfile(user.id);
const posts = await fetchPosts(profile.id);
return posts;
} catch (error) {
console.error(error);
}
}
// 조건부 비동기 처리
async function conditionalFetch(condition) {
const user = await fetchUser();
if (condition) {
const extraData = await fetchExtra();
return { user, extraData };
}
return { user };
}
// 1. 동시성 처리
async function concurrentProcess() {
// 병렬 실행
const dataPromise = fetchData();
const userPromise = fetchUser();
// 순차적 처리가 필요한 경우
const data = await dataPromise;
const user = await userPromise;
return { data, user };
}
// 2. 에러 처리 패턴
async function errorHandling() {
try {
const result = await riskyOperation();
} catch (error) {
if (error instanceof NetworkError) {
await handleNetworkError(error);
} else if (error instanceof ValidationError) {
await handleValidationError(error);
} else {
throw error; // 처리할 수 없는 에러는 상위로 전파
}
}
}
// 3. 타임아웃 처리
async function fetchWithTimeout(url, timeout = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, {
signal: controller.signal
});
clearTimeout(timeoutId);
return response;
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error('Request timed out');
}
throw error;
}
}
단점:
- 병렬 처리를 위해서는 Promise.all 등을 함께 사용해야 함
- await 키워드를 빼먹으면 의도치 않은 동작 발생
- async 함수는 항상 Promise를 반환
3. 실제 사용 예시와 비교
// Promise 버전
function processData() {
return fetchData()
.then(data => processStep1(data))
.then(result1 => processStep2(result1))
.then(result2 => processStep3(result2))
.catch(error => handleError(error));
}
// async/await 버전
async function processData() {
try {
const data = await fetchData();
const result1 = await processStep1(data);
const result2 = await processStep2(result1);
return await processStep3(result2);
} catch (error) {
handleError(error);
}
}
// 병렬 처리
async function parallelProcessing() {
try {
const [result1, result2] = await Promise.all([
processTask1(),
processTask2()
]);
return combineResults(result1, result2);
} catch (error) {
handleError(error);
}
}
// 순차 실행 (비효율적)
async function sequentialProcessing() {
try {
const result1 = await processTask1();
const result2 = await processTask2();
return combineResults(result1, result2);
} catch (error) {
handleError(error);
}
}
// 병렬 실행을 유지하면서 async/await 사용 (효율적)
async function parallelProcessing() {
try {
// 두 작업이 동시에 시작됨
const task1Promise = processTask1(); // 즉시 실행 시작
const task2Promise = processTask2(); // 즉시 실행 시작
// 작업들이 이미 실행 중이며, 여기서는 결과만 기다림
const result1 = await task1Promise;
const result2 = await task2Promise;
return combineResults(result1, result2);
} catch (error) {
handleError(error);
}
}
실무 선호도
- 일반적인 비동기: async/await
- 병렬 처리: Promise.all + async/await
- 복잡한 분기 처리: async/await
- async/await를 주로 사용하는 이유:
- 코드 가독성이 높음
- 디버깅이 용이
- try-catch로 에러 처리가 직관적
- 동기 코드와 유사한 구조로 유지보수 용이
- Promise를 사용하는 경우:
- 병렬 처리 (Promise.all)
- 경쟁 상태 처리 (Promise.race)
- 이미 Promise 기반으로 작성된 레거시 코드 유지보수
// 일반적인 패턴
async function getUserData(userId) {
try {
const user = await fetchUser(userId);
const posts = await fetchUserPosts(userId);
return { user, posts };
} catch (error) {
logger.error(error);
throw new CustomError('사용자 데이터 조회 실패');
}
}
// 병렬 처리가 필요한 경우
async function getDashboardData() {
const [users, products, orders] = await Promise.all([
fetchUsers(),
fetchProducts(),
fetchOrders()
]);
return { users, products, orders };
}