잘못 이해하고 썼던 시간을 반성하며 제대로 사용하는 방법을 공유합니다. 누군가는 같은 실수를 하지 않길 바라면서..
N개의 비동기 처리를 동시 실행하고 다 완료되면 반영하는 작업이 필요할 때 큰 고민 없이 Promise.all
을 썼는데요. 요청들 중에서 하나라도 reject 되거나 exception이 발생하면 모두 실패한 걸로 처리된다는 걸 이제야 알았네요. (털썩)
async getListData() {
...
}
async getExtraData() {
...
}
async getAllData() {
try {
const [
listData,
extraData,
] = await Promise.all([this.getListData(), this.getExtraData()]);
this.drawListData(listData);
this.drawExtraData(extraData);
} catch (e) {
console.error(e);
}
}
위 예제에서 getExtraData()
를 처리하던 중 문제가 발생하면 drawListData()
도 호출되지 못했던 거죠. 이런 상황에 아무것도 출력되지 않는 것보다는 성공한 부분은 보여주는 것이 사용성에 좋습니다. "어떻게 해야 할까?" 고민하며 찾아보니 모두 수행하고 요청 별로 성공과 실패를 전달하는 Promise.allSettled()가 있더군요.
const [
listData,
extraData,
] = await Promise.allSettled([this.getListData(), this.getExtraData()]);
// listData = { status: 'fulfilled', value: ... };
// extraData = { status: 'rejected', reason: ... };
스펙 문서를 보면 어떻게 동작하는지 쉽게 이해가 되실 겁니다. 요청 별로 처리 결과(status)를 알 수 있고 실제 반환된 값은 value에 전달됩니다. 문제는 tc39 stage4에 2019년 7월에 추가된 따끈따끈한 기능이라 모든 자바스크립트 엔진에서 지원하지 못하고 RN에서도 사용할 수 없는 점입니다. 하지만 여러 멋쟁이들이 만든 Polyfill들이 많고 자체 구현도 어렵지 않답니다.
export function allPromisesSettled(promises) {
const onFulfilled = value => ({
status: 'fulfilled',
value,
});
const onRejected = reason => ({
status: 'rejected',
reason,
});
const settledPromises = promises.map(
promise => Promise.resolve(promise).then(onFulfilled, onRejected),
);
return Promise.all(settledPromises);
}
간단하게 구현해서
async getAllData() {
const [
listData,
extraData,
] = await allPromisesSettled([this.getListData(), this.getExtraData()]);
if (listData?.status === 'fulfilled') {
this.drawListData(listData.value);
}
if (extraData?.status === 'fulfilled') {
this.drawExtraData(extraData.value);
}
}
결과 상태를 체크해서 처리되도록 수정하면 의도한 대로 동작합니다.
안다고 자만하지 말고 봤다고 지나치지 말고 스펙 문서는 꼼꼼하게 읽는 습관을 가지자!