...
Promise.all 의 문제점
Promise.all([ promise1, promise2, ... ]) 의 형태로 사용되며, 배열로 받은 모든 프로미스가 fulfill 된 이후, 모든 프로미스의 반환 값을 배열에 넣어 반환한다.
그런데 만약 배열에 있는 프로미스 중 하나라도 reject가 호출된다면, 성공한 프로미스 응답은 무시된채로 그냥 바로 catch로 빠져버리게 된다.
const promise1 = new Promise((resolve) => {
setTimeout(() => {
resolve(1);
}, 3000);
});
const promise2 = new Promise((resolve) => {
setTimeout(() => {
resolve(2);
}, 2000);
});
const promise3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('다 무시하고 에러내버려!'));
}, 2500);
});
Promise.all([promise1, promise2, promise3])
.then((result) => console.log(result))
.catch((e) => console.error(e));
위의 예제는 단순한 코드라 잘 와닿지 않겠지만, 다음과 같이 비동기 api로 서버에 요청을 보낸다고 가정해보자.
const req1 = axios.post('서버주소', { obj1 });
const req2 = axios.post('서버주소', { obj2 });
const req3 = axios.post('서버주소', { obj3 });
const req4 = axios.post('서버주소', { obj4 });
const req5 = axios.post('서버주소', { obj5 });
Promise.all([req1, req2, req3, req4, req5])
.then((result) => console.log(result))
.catch((err) => console.log(err));
위처럼 한번에 너무 많은 Request 를 서버에 날리게 되면 서버에 과부하를 줄 수 있다.
그래서 만일 나머지 요청은 문제없이 잘 됬는데, req3 요청만 에러가 생겨 reject되었다고 가정하자.그러면 다시 요청을 보내야 되는데, 또 req 1 ~ 5 개의 요청을 한꺼번에 보내야 된다. 실패한 req3 요청만 다시 보내서 응답 받으면 되지 비효율적으로 작업 처리를 하고 있는 것이다.
Promise.allSettled
Promise.all의 경우, 하나의 프로미스라도 실패하면 에러로 처리되었다.
반면, Promise.allSettled는 여러 프로미스를 병렬적으로 처리하되, 하나의 프로미스가 실패해도 무조건 이행한다.
Promise.allSettled([ promise1, promise2,...]) 의 형태로 이 역시 Promise.all과 동일한 형태로 실행한다. 하지만 반환값은 Promise.all과 매우 다르다.
배열로 받은 모든 프로미스의 fulfilled, reject 여부와 상관없이, 전부 완료만 되었다면(not pending) 해당 프로미스들의 결과를 배열로 리턴한다.
앞에서 했던 Promise.all 코드를 Promise.allSettled 로 바꿔보자.
const promise1 = new Promise((resolve) => {
setTimeout(() => {
resolve(1);
}, 3000);
});
const promise2 = new Promise((resolve) => {
setTimeout(() => {
resolve(2);
}, 2000);
});
const promise3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('다 무시하고 에러내버려!'));
}, 2500);
});
Promise.allSettled([promise1, promise2, promise3])
.then((result) => console.log(result))
.catch((e) => console.error(e));
위와같이 배열에 status와 value 속성을 담응 객체를 반환해주는 걸 볼 수 있다.
성공하면 status 필드에 fulfilled(이행된) 값을 넣고, 실패하면 rejected(거부된) 값을 넣는다.
이제 이 배열을 순회해서 값을 얻으면 되는 것이다.
이제 서버 요청 Promise.all을 다음과 같이 실패한것들만 따로 필터링해서 재요청 보내는 식으로 응용할 수 있게 되었다.
const req1 = axios.post('서버주소', { obj1 });
const req2 = axios.post('서버주소', { obj2 });
const req3 = axios.post('서버주소', { obj3 });
const req4 = axios.post('서버주소', { obj4 });
const req5 = axios.post('서버주소', { obj5 });
const postArr = [req1, req2, req3, req4, req5];
Promise.allSettled(postArr)
.then((result) => {
// 실패한 것들만 필터링해서 다시 시도
result.forEach(async (val, index) => {
if(val.status === 'rejected') {
await postArr[index]; // 실패한 요청 다시 ajax
}
})
})
.catch((err) => console.log(err));
Promise.allSettled 폴리필
그렇다고 너무 Promise.allSettled 를 맹목적으로 믿고 사용하면 안된다.
Promise.all 의 상위호환이 아닌 편의성을 위해 구현된 기능이기 때문에 필요한 기능을 적절한 판단으로 적재적소에 사용하는게 좋다. 그리고 아직 표준에 등제된지 얼마되지 않은 따끈따끈한 기능이라, 구형 브라우저나 LTS 버젼 Node.js 를 사용한다면 Pollyfill 를 따로 구현 해줘야 한다.
/* 폴리필 코드 */
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
}))));
};
}
#참고자료
https://dmitripavlutin.com/promise-all/
https://twitter.com/mgechev/status/1300682318114361345
이 글이 좋으셨다면 구독 & 좋아요
여러분의 구독과 좋아요는
저자에게 큰 힘이 됩니다.