Joyful 디버깅

[JS] async/await 사용 시 Promise { <pending> } 이 뜨는 이유 ? Promise의 상태, Promise 메서드, Promise.all / Promise.race / Promise.allSettled

개발자 조이 2021. 8. 6. 15:33
728x90
반응형

재고를 확인하고 싶은 상품들의 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 상태는 어떤 상태일까?

 

 

 

 

Promise3가지 상태


출처 : MDN 공식문서

  • 대기(pending): 이행하거나 거부되지 않은 초기 상태.
  • 이행(fulfilled): 연산이 성공적으로 완료됨.
  • 거부(rejected): 연산이 실패함.

 

그러니까, 현재 내 로직에서 return 된 Promise 배열은 아무것도 실행되지 않은 초기 상태의 Promise라는 것이다. 

 

await는 Promise 객체를 실행하고 기다리지만, 지금 내 코드에서는 Array.map으로 Promise의 배열을 반환하게만 할 뿐, 실행이 되진 않았기 때문에 결과 값에 pending 상태의 Promise 배열만 남아있게 된 것이다.

 

어떻게 해야 pending 상태의 Promise들을 실행시켜줄 수 있을까?

 

 

 

Promise 메서드


 

MDN 공식문서에서 Promise에 대해 알아보니, Promise는 몇 가지 메서드를 가지고 있다.

 

출처 : MDN 공식문서 - Promise

 


 

이 중 iterable을 파라미터로 받는 Promise.allPromise.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

https://ko.javascript.info/promise-api

728x90
반응형