[JS] async/await 사용 시 Promise { <pending> } 이 뜨는 이유 ? Promise의 상태, Promise 메서드, Promise.all / Promise.race / Promise.allSettled
재고를 확인하고 싶은 상품들의 ID값을 배열로 받아 map 함수를 돌며 DB를 확인해, 재고가 부족한 상품이 있을 경우 해당 상품을 return 해주는 기능을 구현하고 있었다.
문제 상황
async/await 함수를 이용해 map함수가 전부 실행된 후에 outOfStockList를 response 해주는 로직이다.
const checkCountOfStock = async (req, res) => {
try {
const outOfStockList = await req.body.map(async (product) => {
const outOfStock = await Product.findOne({
_id: product.productId,
countInStock: { $lt: product.countOfOrder },
})
return outOfStock;
});
console.log(outOfStockList);
res.json(outOfStockList);
} catch (error) {
console.error(error);
res.status(500).json({ message: "Server Error" });
}
};
위의 코드를 실행하면 response 값에 재고가 부족한 product 값들이 담길 거라는 내 예상과는 다르게 console.log로 찍어본 outOfStockList에는 아래와 같은 값이 담겨있다.
// 실행 결과
[ Promise {<pending>}, Promise {<pending>}]
일단 async/await를 이용해 내가 의도한 순서대로 map함수가 돌고 난 후에 outOfStockList 값을 response 하고 있긴 한데, 내가 원하는 값이 아닌 pending상태의 Promise 배열이 return 되었다.
Promise의 pending 상태는 어떤 상태일까?
Promise의 3가지 상태
- 대기(pending): 이행하거나 거부되지 않은 초기 상태.
- 이행(fulfilled): 연산이 성공적으로 완료됨.
- 거부(rejected): 연산이 실패함.
그러니까, 현재 내 로직에서 return 된 Promise 배열은 아무것도 실행되지 않은 초기 상태의 Promise라는 것이다.
await는 Promise 객체를 실행하고 기다리지만, 지금 내 코드에서는 Array.map으로 Promise의 배열을 반환하게만 할 뿐, 실행이 되진 않았기 때문에 결과 값에 pending 상태의 Promise 배열만 남아있게 된 것이다.
어떻게 해야 pending 상태의 Promise들을 실행시켜줄 수 있을까?
Promise의 메서드
MDN 공식문서에서 Promise에 대해 알아보니, Promise는 몇 가지 메서드를 가지고 있다.
이 중 iterable을 파라미터로 받는 Promise.all과 Promise.race, Promise.allSettled(공식문서에는 없지만 새로 추가된 메서드)를 살펴보자.
- Promise.all(iterable)
-> 프라미스 결과가 모두 resolve 되어야만 할 때 사용
전체가 프라미스인 배열을 받아 실행하고, 새로운 프라미스를 반환하는 메서드이다.
배열 안에 있는 프라미스가 모두 처리되면, 새로운 프라미스가 이행되는데, 배열 안 프라미스의 결괏값을 담은 배열이 새로운 프라미스의 결괏값이 된다. 배열의 프라미스 중 하나라도 reject 발생 시 Promise.all은 멈추고, 에러 처리되어, 지금까지 이행되었던 모든 프라미스도 사라지게 된다.
- Promise.race(iterable)
Promise.all과 비슷하지만, 가장 먼저 처리되는 프라미스의 결괏값만을 반환하는 메서드이다.
resolve나 reject 상관없이 가장 먼저 수행된 결과만을 받아올 수 있다.
- Promise.allSettled(iterable)
-> 프라미스가 reject 되어도 모두 다 실행해야 할 때 사용
(추가된 지 얼마 되지 않은 메서드이기 때문에 구식 브라우저는 폴리필이 필요하다고 한다.)
모든 프라미스가 처리될 때까지 기다린다. Promise.all과는 다르게 reject가 발생해도 다른 프라미스의 이행 결과는 사라지지 않는다. Promise.allSettled의 결괏값엔 각 프라미스의 resolve와 reject여부와 결괏값이 담긴다.
Promise의 메서드에 대한 더 자세한 설명은 여기에서 확인 가능하다.
문제 해결
내 경우에는 모든 프라미스를 resolve 시켜야 하기 때문에 프라미스 메서드 중 Promise.all을 사용했다.
Array.map를 사용하는 함수를 Promise.all로 감싸주었더니, 문제가 해결되었다.
const checkCountOfStock = async (req, res) => {
try {
const outOfStockList = await Promise.all(
req.body.map(async (product) => {
const outOfStock = await Product.findOne({
_id: product.productId,
countInStock: { $lt: product.countOfOrder },
})
return outOfStock;
})
);
res.json(outOfStockList);
} catch (error) {
console.error(error);
res.status(500).json({ message: "Server Error" });
}
};
참고
https://stackoverflow.com/questions/46072479/promise-pending-trying-to-await-for-map
https://velog.io/@minsangk/2019-09-06-0209-%EC%9E%91%EC%84%B1%EB%90%A8-eik06xy8mm