Javascript : Promise 객체 : 최신 개념과 실전 활용법
자바스크립트의 Promise 객체: 최신 개념과 실전 활용법
비동기 작업을 보다 효율적으로 관리하는 데 필수적인 JavaScript의 Promise는 최신 버전에서도 다양한 개선을 통해 주니어 개발자들이 쉽게 비동기 작업을 다룰 수 있도록 발전하고 있습니다. 이번 글에서는 Promise의 핵심 기능을 최신 자료에 기반해 정리하고, 자주 쓰이는 메서드들을 설명해 보겠습니다.
1. Promise의 기본 개념
Promise는 자바스크립트에서 비동기 작업의 상태를 관리하는 객체로, 비동기 작업이 완료되거나 실패했을 때 이를 처리하는 방법을 미리 정의할 수 있습니다. resolve 또는 reject가 호출될 때, 해당 Promise의 상태는 fulfilled 혹은 _rejected_로 변경됩니다. 이를 통해 코드를 더욱 간결하게 작성하고, 비동기 작업이 순차적으로 실행될 수 있도록 도와줍니다.
2. 주요 Promise 메서드
2.1 Promise.all()
Promise.all()
은 여러 비동기 작업을 병렬로 실행하고, 모든 작업이 성공했을 때 그 결과를 배열로 반환합니다. 단, 작업 중 하나라도 실패하면 전체가 실패로 처리됩니다.
const promise1 = Promise.resolve(5);
const promise2 = Promise.resolve(33);
const promise3 = new Promise((resolve) => setTimeout(resolve, 2000, '완료!'));
Promise.all([promise1, promise2, promise3])
.then((results) => console.log(results)); // [5, 33, "완료!"]
이 메서드는 다수의 작업을 동시에 실행할 때 유용하지만, 하나라도 실패하면 전체가 실패로 처리된다는 점을 유의해야 합니다.
2.2 Promise.allSettled()
Promise.allSettled()
는 Promise.all()
과 비슷하지만, 모든 Promise가 완료될 때까지 기다리며 성공 여부에 관계없이 결과를 반환합니다. 이 경우 각 작업의 상태와 결과를 모두 알 수 있습니다.
const promise1 = Promise.resolve(9);
const promise2 = new Promise((_, reject) => setTimeout(reject, 4000, '에러 발생!'));
Promise.allSettled([promise1, promise2])
.then((results) => console.log(results));
// [{ status: "fulfilled", value: 9 }, { status: "rejected", reason: "에러 발생!" }]
실패한 작업도 함께 기록하기 때문에, 여러 작업의 결과를 한 번에 처리하는 상황에 적합합니다
2.3 Promise.any()
Promise.any()
는 여러 Promise 중 가장 먼저 성공한 하나만 반환하며, 나머지는 무시합니다. 이는 다수의 작업 중 하나만 성공하면 충분한 경우에 유용합니다. 단, 모든 작업이 실패하면 AggregateError라는 특별한 에러가 발생합니다.
const promise1 = Promise.reject(0);
const promise2 = new Promise((resolve) => setTimeout(resolve, 2000, '2초 후 성공!'));
Promise.any([promise1, promise2])
.then((result) => console.log(result)) // "2초 후 성공!"
.catch((error) => console.log(error.errors)); // AggregateError
이 메서드는 특정 작업이 먼저 완료되는지 확인해야 할 때 유용하며, 최초 성공한 Promise의 값만 반환하는 것이 특징입니다
2.4 Promise.race()
Promise.race()
는 가장 먼저 완료된 Promise의 결과를 반환합니다. 결과가 성공이든 실패이든 가장 빠르게 완료된 작업이 반환됩니다.
const promise1 = new Promise((resolve) => setTimeout(resolve, 3000, '첫 번째 성공'));
const promise2 = new Promise((resolve) => setTimeout(resolve, 1000, '두 번째 성공'));
Promise.race([promise1, promise2])
.then((result) => console.log(result)); // "두 번째 성공"
이 메서드는 경쟁 상황에서 가장 빠르게 처리된 작업의 결과를 활용해야 할 때 유용합니다
3. 결론 및 활용 팁
비동기 작업을 효율적으로 관리하는 자바스크립트의 Promise는 개발자가 비동기 코드의 복잡성을 줄이고, 더욱 직관적인 코드 작성을 가능하게 합니다. Promise.all()
, Promise.allSettled()
, Promise.any()
, Promise.race()
와 같은 다양한 메서드를 활용하여 다양한 상황에서 비동기 작업을 최적화할 수 있습니다.
주니어 개발자들을 위한 팁:
- 다중 비동기 작업 관리: 여러 비동기 작업을 한꺼번에 처리하고 싶다면
Promise.all()
과Promise.allSettled()
를 적절히 활용해 보세요. - 속도 우선 처리: 작업 중 하나만 성공하면 되거나, 첫 번째로 완료된 작업이 중요할 경우
Promise.any()
또는Promise.race()
를 사용해 보세요.
아래는 복잡한 비동기 작업을 다루는 자바스크립트 Promise 예제입니다. 코드 내부에 각 메서드의 기능과 동작 원리에 대한 설명이 주석으로 기재되어 있습니다.
// 여러 비동기 작업을 복합적으로 처리하는 예제
// Promise 객체 3개를 생성합니다.
// 각 작업은 비동기적으로 실행되며, 일정 시간이 지난 후에 완료됩니다.
const task1 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('Task 1 완료');
resolve('Task 1 결과');
}, 3000); // 3초 후에 완료
});
const task2 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('Task 2 실패');
reject('Task 2 실패'); // 작업 2는 실패합니다.
}, 2000); // 2초 후에 실패
});
const task3 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('Task 3 완료');
resolve('Task 3 결과');
}, 4000); // 4초 후에 완료
});
// 1. Promise.all() 사용 예제
// 모든 작업이 성공했을 때만 결과를 반환합니다.
// 하나라도 실패하면 전체가 실패로 처리됩니다.
Promise.all([task1, task2, task3])
.then(results => {
console.log('Promise.all 성공:', results);
})
.catch(error => {
console.log('Promise.all 실패:', error);
});
// task2가 실패하므로 Promise.all은 실패 처리됩니다.
// 2. Promise.allSettled() 사용 예제
// 각 작업의 성공 여부와 상관없이 모든 작업이 완료되면 결과를 반환합니다.
// 각각의 결과는 { status: 'fulfilled' or 'rejected', value or reason } 형태로 반환됩니다.
Promise.allSettled([task1, task2, task3])
.then(results => {
console.log('Promise.allSettled 결과:');
results.forEach((result, index) => {
console.log(`Task ${index + 1}:`, result);
});
});
// 모든 작업의 상태를 알 수 있으므로, 실패한 작업과 성공한 작업을 구분할 수 있습니다.
// 3. Promise.any() 사용 예제
// 가장 먼저 성공한 작업의 결과만 반환합니다.
// 만약 모든 작업이 실패하면 AggregateError가 발생합니다.
Promise.any([task1, task2, task3])
.then(result => {
console.log('Promise.any 성공:', result);
})
.catch(error => {
console.log('Promise.any 실패:', error.errors); // 실패한 이유들을 출력합니다.
});
// task1이 task2보다 늦게 완료되므로 task1의 결과를 반환합니다.
// 4. Promise.race() 사용 예제
// 가장 빨리 완료된 작업의 결과를 반환합니다. 성공이든 실패든 가장 먼저 완료된 작업의 결과가 반환됩니다.
Promise.race([task1, task2, task3])
.then(result => {
console.log('Promise.race 결과:', result);
})
.catch(error => {
console.log('Promise.race 실패:', error);
});
// task2가 가장 빨리 실패하므로 실패 결과가 출력됩니다.
코드 설명
- Promise.all(): 모든 작업이 성공할 때만 결과를 반환하며, 하나라도 실패하면 전체가 실패로 처리됩니다. 이 경우
task2
가 실패하기 때문에Promise.all
은 실패로 처리됩니다. - Promise.allSettled(): 모든 작업의 완료 여부와 상관없이 각 작업의 상태와 결과를 반환합니다. 각 작업의 성공 여부를 쉽게 확인할 수 있는 장점이 있습니다.
- Promise.any(): 여러 작업 중 가장 먼저 성공한 작업만 반환하고, 모든 작업이 실패할 경우
AggregateError
를 발생시킵니다. 이 예제에서는task1
이 성공하여 그 결과가 반환됩니다. - Promise.race(): 가장 빨리 완료된 작업의 결과를 반환합니다. 성공 또는 실패와 상관없이 가장 먼저 완료된 작업의 결과가 출력됩니다. 이 경우
task2
가 가장 빨리 실패하여 그 결과가 반환됩니다.
이 예제는 다양한 상황에서 Promise를 어떻게 활용할 수 있는지를 보여줍니다. 복잡한 비동기 작업에서 Promise를 효과적으로 관리하는 방법을 이해하는 데 도움이 됩니다.
아래는 POST 메서드를 사용하고 파라미터를 전달하는 고급 예제입니다. 여러 개의 API 요청을 동시에 처리하고, 각 요청에 대해 POST 방식으로 데이터를 전송하며, 다양한 상황에 맞게 Promise.all(), Promise.allSettled(), Promise.any(), Promise.race()를 활용하는 방법을 보여줍니다.
아래는 주어진 자바스크립트 코드를 HTML 페이지에서 사용할 수 있도록 한 예제입니다. HTML 페이지를 브라우저에서 실행하면 여러 API 요청을 비동기적으로 처리하며, POST 요청의 성공 및 실패 여부를 콘솔에 출력합니다.
고급화된 예제 코드 (POST 방식, 파라미터 포함)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Promise API Example</title>
</head>
<body>
<h1>Promise API Example with POST Requests</h1>
<button id="start">Start API Requests</button>
<script>
// POST로 데이터를 전송하는 API URL
const apiURL1 = 'https://jsonplaceholder.typicode.com/posts';
const apiURL2 = 'https://jsonplaceholder.typicode.com/posts';
const apiURL3 = 'https://jsonplaceholder.typicode.com/posts';
// POST 데이터를 준비하는 함수
const createPostData = (title, body, userId) => {
return JSON.stringify({
title: title,
body: body,
userId: userId
});
};
// POST 요청을 처리하는 함수 (fetch로 POST 요청)
const postData = (url, data) => {
return fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: data
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
})
.catch(error => {
console.error(`Error posting to ${url}:`, error);
throw error;
});
};
// API 요청을 트리거하는 함수
document.getElementById('start').addEventListener('click', () => {
// 전송할 데이터를 정의
const data1 = createPostData('첫 번째 제목', '첫 번째 본문 내용', 1);
const data2 = createPostData('두 번째 제목', '두 번째 본문 내용', 2);
const data3 = createPostData('세 번째 제목', '세 번째 본문 내용', 3);
// 1. Promise.all(): 모든 요청이 성공해야만 다음 단계로 진행
Promise.all([postData(apiURL1, data1), postData(apiURL2, data2), postData(apiURL3, data3)])
.then((results) => {
console.log('Promise.all 성공:', results);
})
.catch((error) => {
console.error('Promise.all 실패:', error);
});
// 2. Promise.allSettled(): 각 요청의 성공 여부와 상관없이 모든 결과를 반환
Promise.allSettled([postData(apiURL1, data1), postData(apiURL2, data2), postData(apiURL3, data3)])
.then((results) => {
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`API ${index + 1} 성공:`, result.value);
} else {
console.error(`API ${index + 1} 실패:`, result.reason);
}
});
});
// 3. Promise.any(): 가장 먼저 성공한 요청만 처리
Promise.any([postData(apiURL1, data1), postData(apiURL2, data2), postData(apiURL3, data3)])
.then((result) => {
console.log('Promise.any 첫 번째 성공:', result);
})
.catch((error) => {
console.error('Promise.any 모든 요청 실패:', error.errors);
});
// 4. Promise.race(): 가장 빠르게 완료된 요청만 처리
Promise.race([postData(apiURL1, data1), postData(apiURL2, data2), postData(apiURL3, data3)])
.then((result) => {
console.log('Promise.race 첫 번째 완료:', result);
})
.catch((error) => {
console.error('Promise.race 첫 번째 실패:', error);
});
});
</script>
</body>
</html>
이 HTML 예제는 실제로 fetch를 이용하여 POST 방식으로 데이터를 전송하고, 각각의 Promise 메서드가 어떻게 동작하는지를 실시간으로 확인할 수 있도록 구성되었습니다.
실행결과
실무 유형 및 병렬 처리 방법
이 코드는 웹 애플리케이션 개발에서 비동기 작업을 관리할 때 사용됩니다. 특히, 여러 개의 API 요청을 동시에 실행하거나, 각 요청의 결과를 개별적으로 처리해야 하는 복잡한 비동기 상황에서 매우 유용합니다. 이러한 기술은 다음과 같은 실제 상황에서 적용할 수 있습니다:
1. 병렬로 여러 API 요청 처리
- 상황: 여러 외부 API에서 데이터를 동시에 받아와야 하는 경우.
- 적용 예: 상품 정보, 사용자 정보, 주문 내역 등 여러 REST API로부터 동시에 데이터를 가져와서 페이지에 표시하는 상황.
- 사용 방법: Promise.all()을 사용하여 모든 API가 성공적으로 응답했을 때에만 데이터를 처리하고 렌더링합니다.
2. 서로 독립적인 API 요청 처리
- 상황: 여러 API 요청 중 일부가 실패하더라도 다른 요청을 계속해서 처리해야 하는 경우.
- 적용 예: 대시보드 페이지에서 여러 데이터 소스를 불러올 때, 일부 데이터는 실패해도 나머지 데이터를 보여줄 수 있어야 하는 상황.
- 사용 방법: Promise.allSettled()을 사용하여 각 API의 성공 여부에 상관없이 데이터를 처리할 수 있습니다. 실패한 요청에 대해서는 별도의 오류 메시지를 표시할 수 있습니다.
3. 가장 빠른 응답을 사용
- 상황: 여러 서버 또는 API 중에서 가장 빠른 응답을 사용하는 경우.
- 적용 예: 동일한 데이터에 대해 여러 서버에서 요청을 보내고, 가장 빠른 서버의 응답을 사용하여 유저에게 빠르게 결과를 제공하는 상황.
- 사용 방법: Promise.race()를 사용하여 가장 빠르게 응답한 API의 결과를 사용하고, 나머지 요청은 무시하거나 취소합니다.
4. 최소한 하나의 성공만 필요
- 상황: 여러 API 요청 중 하나만 성공하면 충분한 경우.
- 적용 예: 여러 서버에 백업된 데이터를 불러올 때, 하나의 서버만 성공적으로 응답해도 해당 데이터를 사용하면 되는 상황.
- 사용 방법: Promise.any()를 사용하여 가장 먼저 성공한 요청의 결과만 처리하고, 모든 요청이 실패하면 에러 처리 로직을 수행합니다.
5. 비동기 작업의 상태 관리
- 상황: 각 요청의 상태(성공, 실패)를 추적하면서도, 특정 비동기 작업의 완료 여부에 따라 동작을 달리해야 하는 상황.
- 적용 예: 예약 시스템에서 여러 예약 확인 API를 호출하고, 각 예약 상태를 보여주는 작업.
- 사용 방법: 각 Promise 메서드를 사용하여 요청이 완료될 때마다 개별 상태에 따라 적절한 화면을 업데이트하거나, 백엔드 로직을 실행합니다.
실무에서 활용할 수 있는 구체적인 예:
- 전자상거래 플랫폼에서 여러 물류 시스템의 재고를 동시에 조회하여 빠르게 고객에게 정보를 제공하는 경우.
- 여행 예약 시스템에서 항공편, 호텔, 렌터카 예약 정보를 동시에 받아와 각 서비스의 가용성을 체크하고, 사용자에게 결과를 빠르게 제공.
- 금융 애플리케이션에서 여러 금융 기관의 환율 정보를 병렬로 받아와 가장 빠르게 응답한 환율 데이터를 기준으로 환전 비율을 계산.
이처럼, 비동기 작업을 효율적으로 관리하는 것이 중요할 때 이러한 Promise 메서드를 적용하여 개발을 최적화할 수 있습니다.