Promise.allSettled 완벽 가이드: 병렬 비동기 처리의 새로운 패러다임
목차
- 소개
- Promise.allSettled vs Promise.all
- Promise.allSettled의 동작 방식
- 실전 예제
- 성능 고려사항
- 브라우저 지원 및 폴리필
- 모범 사례와 패턴
- 결론
소개
현대 웹 애플리케이션에서 여러 비동기 작업을 동시에 처리해야 하는 상황은 매우 흔합니다. 여러 API를 호출하거나, 다수의 파일을 처리하거나, 데이터베이스 쿼리를 병렬로 실행하는 등의 작업이 그 예입니다. Promise.allSettled()
는 이러한 복잡한 비동기 시나리오를 우아하게 처리할 수 있게 해주는 강력한 도구입니다.
ES2020에서 도입된 Promise.allSettled()
는 기존의 Promise.all()
이 가진 한계를 보완하여, 모든 프로미스의 실행 결과를 보장받을 수 있게 해줍니다.
Promise.allSettled vs Promise.all
Promise.all의 한계
Promise.all()
은 여러 프로미스를 병렬로 처리할 때 널리 사용되어 왔지만, 중요한 제한사항이 있습니다:
// Promise.all 예제
const promises = [
fetch('https://api.example.com/data1'),
fetch('https://api.example.com/data2'),
fetch('https://api.example.com/data3')
];
Promise.all(promises)
.then(results => console.log('모든 요청 성공:', results))
.catch(error => console.error('하나라도 실패하면 여기서 캐치:', error));
위 코드의 문제점은 하나의 프로미스라도 실패하면 전체가 실패로 처리된다는 것입니다. 실제 운영 환경에서는 일부 작업의 실패가 전체 프로세스를 중단시키지 않아야 하는 경우가 많습니다.
Promise.allSettled의 장점
Promise.allSettled()
는 이러한 한계를 극복하여 다음과 같은 이점을 제공합니다:
- 모든 프로미스의 완료를 기다립니다
- 성공과 실패 여부에 관계없이 모든 결과를 받을 수 있습니다
- 각 프로미스의 상태와 결과값을 상세히 확인할 수 있습니다
// Promise.allSettled 예제
const promises = [
fetch('https://api.example.com/data1'),
fetch('https://api.example.com/data2'),
fetch('https://api.example.com/data3')
];
Promise.allSettled(promises)
.then(results => {
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`요청 ${index + 1} 성공:`, result.value);
} else {
console.log(`요청 ${index + 1} 실패:`, result.reason);
}
});
});
Promise.allSettled의 동작 방식
Promise.allSettled()
는 프로미스 배열을 입력받아 새로운 프로미스를 반환합니다. 반환된 프로미스는 모든 입력 프로미스가 완료(성공 또는 실패)되었을 때 이행됩니다.
반환값의 구조
각 결과 객체는 다음과 같은 구조를 가집니다:
// 성공한 경우
{
status: 'fulfilled',
value: /* 성공 결과값 */
}
// 실패한 경우
{
status: 'rejected',
reason: /* 실패 사유 */
}
실전 예제
1. 여러 API 동시 호출
async function fetchUserData() {
const endpoints = [
'https://api.example.com/user/profile',
'https://api.example.com/user/posts',
'https://api.example.com/user/friends'
];
const promises = endpoints.map(url => fetch(url));
const results = await Promise.allSettled(promises);
const successResults = results
.filter(result => result.status === 'fulfilled')
.map(result => result.value);
const failedResults = results
.filter(result => result.status === 'rejected')
.map(result => result.reason);
return {
successful: successResults,
failed: failedResults
};
}
2. 파일 업로드 처리
async function uploadMultipleFiles(files) {
const uploadPromises = files.map(file => {
return new Promise((resolve, reject) => {
const formData = new FormData();
formData.append('file', file);
fetch('/upload', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(resolve)
.catch(reject);
});
});
const results = await Promise.allSettled(uploadPromises);
// 업로드 결과 분석
const summary = {
total: files.length,
successful: 0,
failed: 0,
failedFiles: []
};
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
summary.successful++;
} else {
summary.failed++;
summary.failedFiles.push({
name: files[index].name,
error: result.reason
});
}
});
return summary;
}
3. 데이터 동기화 시나리오
async function syncData() {
const tables = ['users', 'posts', 'comments', 'likes'];
const syncPromises = tables.map(table => {
return new Promise(async (resolve, reject) => {
try {
// 마지막 동기화 시간 확인
const lastSync = await getLastSyncTime(table);
// 변경된 데이터 가져오기
const changes = await fetchChanges(table, lastSync);
// 로컬 데이터베이스 업데이트
await updateLocalDB(table, changes);
resolve({ table, changesCount: changes.length });
} catch (error) {
reject({ table, error });
}
});
});
const results = await Promise.allSettled(syncPromises);
// 동기화 결과 보고서 생성
const report = {
timestamp: new Date().toISOString(),
results: results.map(result => {
if (result.status === 'fulfilled') {
return {
table: result.value.table,
status: 'success',
changesApplied: result.value.changesCount
};
} else {
return {
table: result.reason.table,
status: 'error',
error: result.reason.error.message
};
}
})
};
return report;
}
성능 고려사항
Promise.allSettled()
를 사용할 때 고려해야 할 성능 관련 사항들:
메모리 사용량
- 모든 프로미스의 결과를 메모리에 보관하므로, 대량의 프로미스를 처리할 때는 메모리 사용량을 모니터링해야 합니다.
동시성 제어
- 너무 많은 프로미스를 동시에 실행하면 시스템 리소스에 부담이 될 수 있습니다.
- 필요한 경우 배치 처리를 고려하세요.
async function processManyItemsInBatches(items, batchSize = 5) {
const results = [];
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize);
const batchPromises = batch.map(item => processItem(item));
const batchResults = await Promise.allSettled(batchPromises);
results.push(...batchResults);
}
return results;
}
브라우저 지원 및 폴리필
대부분의 최신 브라우저는 Promise.allSettled()
를 지원하지만, 이전 버전의 브라우저를 지원해야 하는 경우 다음과 같은 폴리필을 사용할 수 있습니다:
if (!Promise.allSettled) {
Promise.allSettled = function(promises) {
return Promise.all(promises.map(p => Promise.resolve(p).then(
value => ({
status: 'fulfilled',
value
}),
reason => ({
status: 'rejected',
reason
})
)));
};
}
모범 사례와 패턴
1. 에러 처리 패턴
async function robustDataFetch() {
const results = await Promise.allSettled(promises);
// 에러 로깅
results
.filter(result => result.status === 'rejected')
.forEach(({ reason }) => {
console.error('작업 실패:', reason);
// 에러 모니터링 시스템에 보고
errorReportingService.log(reason);
});
// 성공한 결과만 반환
return results
.filter(result => result.status === 'fulfilled')
.map(result => result.value);
}
2. 재시도 패턴
async function fetchWithRetry(url, retries = 3) {
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
for (let i = 0; i < retries; i++) {
try {
return await fetch(url);
} catch (error) {
if (i === retries - 1) throw error;
await delay(1000 * Math.pow(2, i)); // 지수 백오프
}
}
}
async function robustMultiFetch(urls) {
const promises = urls.map(url => fetchWithRetry(url));
return Promise.allSettled(promises);
}
결론
Promise.allSettled()
는 현대 웹 애플리케이션에서 복잡한 비동기 작업을 처리하는 데 필수적인 도구입니다. 모든 프로미스의 결과를 보장받을 수 있고, 부분적인 실패를 우아하게 처리할 수 있다는 장점이 있습니다.
주요 사용 사례:
- 여러 독립적인 API 호출
- 파일 업로드 처리
- 데이터 동기화
- 배치 작업 처리
Promise.allSettled()
를 효과적으로 활용하면 더 안정적이고 견고한 애플리케이션을 구축할 수 있습니다. 특히 일부 작업의 실패가 전체 프로세스를 중단시키지 않아야 하는 상황에서 매우 유용합니다.
'Javascript' 카테고리의 다른 글
[Javascript; 자바스크립트] 자바스크립트 클로저의 모든 것: 원리, 문제점, 그리고 해결방법 (0) | 2024.11.01 |
---|---|
[Javascript; 자바스크립트] 배열 조작의 기본기: 자바스크립트 push, pop, shift, unshift 사용법 (0) | 2024.10.21 |
[Javascript; 자바스크립트] 자바스크립트에서 세미콜론 사용 가이드 (0) | 2024.09.09 |
[Javascript; 자바스크립트] 천 단위 쉼표 콤마 컴마 내장 함수 toLocaleString() (0) | 2023.06.29 |
[Javascript; 자바스크립트] 비밀번호 정규식 (0) | 2022.09.28 |