Promise.all()
Promise를 활용하는 여러 개의 비동기 작업들을 병렬적으로 처리하고 싶을 때 쓰기 좋은 메서드이다.
지난번에 효율적으로 비동기 작업하는 방법으로 소개되었던 'for문 바깥에 쓰기' 예시 코드를 그대로 가져왔다.
async function getEmployee(id) {
const response = await fetch('https://url.com/api/employees/${id}');
const data = await response.json();
console.log(data);
}
for (let i=1; i<11; i++) {
getEmployee(i);
}
그런데 만약, 비동기 작업 후 받은 결과값을 콘솔 로그로 찍고 싶은 게 아니라 return해서 함수 밖에서 쓰고 싶다면?
async function getEmployee(id) {
const response = await fetch('https://url.com/api/employees/${id}');
const data = await response.json();
// console.log(data);
return data;
}
for (let i=1; i<11; i++) {
// getEmployee(i);
const employee = await getEmployee(i);
}
- 프로미스 형태로 리턴되는 'data'의 결과값을 얻기 위해 getEmployee(i) 앞에 'await'을 붙이고,
- 그 결과값을 새 변수 'const employee'에 담아서 쓰면 된다.
하지만 이렇게 하면 await으로 결과값을 얻는 과정에서 또 시간이 걸리게 된다.
어차피 일괄적으로 프로미스를 풀어야 하는 거라면, 이때 Promise.all()을 사용할 수 있는 것이다.
적용
먼저 for문 안의 await을 해제하고, 프로미스들을 전부 하나의 배열에 담는다.
const promises = [];
for (let i=1; i<11; i++) {
promises.push(getEmployee(i)); // 풀리지 않은 프로미스인 채로 배열에 모두 저장
}
이렇게 하면 await을 하지 않기 때문에 리퀘스트를 거의 병렬적으로(동시에) 보내게 된다.
이때 promises 배열에 추가되는 것은 결과값이 아니라 '프로미스 객체'이다.
여기서 바로 all()을 사용해주면 된다.
const promises = [];
for (let i=1; i<11; i++) {
promises.push(getEmployee(i)); // 풀리지 않은 프로미스인 채로 배열에 모두 저장
}
Promise.all(promises);
Promise.all()을 통해 받은 리턴도 역시 '프로미스'이므로, 결과값까지 받고 싶으면 마찬가지로 await을 통해 풀어줘야 한다.
(리퀘스트 요청 결과 리스폰스 받기에 모두 성공하면 fulfilled, 하나라도 실패하면 rejected 프로미스가 반환됨)
// 최종 코드
const promises = [];
for (let i=1; i<11; i++) {
promises.push(getEmployee(i));
}
const employees = await Promise.all(promises);
console.log(employees); // 직원 정보 결과값 출력
try catch 추가
try catch로 promise.all()의 에러도 핸들링해보자.
이때는 try 블록 안에 프로미스를 담는 작업이 들어가므로, 프로미스 담는 배열을 try catch 블록 바깥에 전역으로 설정해야 한다.
const promises = [];
for (let i=1; i<11; i++) {
promises.push(getEmployee(i));
}
let employees;
try {
employees = await Promise.all(promises);
} catch (error) {
console.log(error);
}
console.log(employees);
응용 문제
두개의 api에 동시에 리퀘스트를 보내고, 직원 데이터는 employees 변수에, 메뉴 데이터는 menus 변수에 저장해 주세요.
// asyncFunctions.js
export async function getEmployees() {
try {
const response = await fetch('https://learn.codeit.kr/api/employees');
const employees = await response.json();
return employees;
} catch (error) {
console.log('데이터를 가져오지 못했습니다 :(');
return null;
}
}
export async function getMenus() {
try {
const response = await fetch('https://learn.codeit.kr/api/menus');
const menus = await response.json();
return menus;
} catch (error) {
console.log('데이터를 가져오지 못했습니다 :(');
return null;
}
}
import { getEmployees, getMenus } from './asyncFunctions.js';
// 코드 작성
// 테스트 코드
console.log('직원 데이터:');
console.log(employees);
console.log('메뉴 데이터:');
console.log(menus);
[답]
import { getEmployees, getMenus } from './asyncFunctions.js';
const employeesPromises = getEmployees();
const menusPromises = getMenus();
const result = await Promise.all([employeesPromises, menusPromises]);
const employees = result[0];
const menus = result[1];
// 테스트 코드
console.log('직원 데이터:');
console.log(employees);
console.log('메뉴 데이터:');
console.log(menus);
[해설]
Promise.all()은 인자를 '배열' 형태로만 받는다.
그래서 await을 쓰지 않고 프로미스들만 받은 객체들인 employeesPromises, menusPromises를 all()에 인자로 담을 때,
'배열'로 감싸서 담지 않으면 객체를 담았다고 에러가 난다.
따라서 Promise.all([ employeesPromises, menusPromises])와 같은 형태로 all()을 사용하고,
마찬가지로 리턴되는 프로미스 형태를 결과값으로 풀기 위해 await을 붙여 'result'라는 변수에 담는다.
이때 result 변수에는 employeesPromises의 결과값, menusPromises의 결과값이 각각 0, 1의 배열 인덱스로 나뉘어서 저장된다.
따라서 이 결과값들을 직원/메뉴 데이터로 각각 나눠서 employees, menus 변수에 저장하려면 배열 인덱스로 접근하면 된다.
=> const employees = result[0];
=> const menus = result[1];
그리고 result부터 menus까지 총 3줄의 코드를 1줄로 줄일 수가 있는데, 바로 'Destructuring(구조 분해 할당)'을 이용하면 된다.
따라서 모범 답안 코드는 아래와 같다.
import { getEmployees, getMenus } from './asyncFunctions.js';
const employeesPromises = getEmployees();
const menusPromises = getMenus();
const [employees, menus] = await Promise.all([employeesPromises, menusPromises]);
// 테스트 코드
console.log('직원 데이터:');
console.log(employees);
console.log('메뉴 데이터:');
console.log(menus);
이제 구조 분해 할당에 대해 감이 좀 잡히나? (나 자신에게 하는 말)
Promise.all() 실행 결과, rejected된 객체가 여러개일 때?
Promise.all()은 등록된 promise중에 하나라도 오류가 발생하면
- 거부된 첫 번째 promise 결과를 반환하고
- 다른 promise 결과는 무시한다.
그래서 만약 promise들의 성공 여부를 각각 배열로 받고 싶다면, Promise.all()이 아닌 Promise.allSettled()을 사용하면 된다.
allSettled()는 각 promise의 결과를 배열로 반환한다.
'JS' 카테고리의 다른 글
| 함수 파라미터를 중괄호로 감싸서 받을 때와 그냥 받을 때의 차이 (0) | 2025.05.10 |
|---|---|
| 자주 사용되는 JavaScript Web API들 (0) | 2025.05.10 |
| 효율적인 비동기 코드 (feat. for문) (0) | 2025.04.25 |
| default export vs named export (0) | 2025.04.22 |
| some()과 every() (0) | 2025.04.22 |