이 블로그는 광고 클릭 수익으로 운영됩니다!
괜찮으시다면 광고 차단을 풀어주세요 ㅠㅠ

게시글

강좌15 - MongoDB - 일 년 전 등록

Mongoose(몽구스) 프로미스

조회수:
0
이 블로그는 광고 클릭 수익으로 운영됩니다!
괜찮으시다면 광고 차단을 풀어주세요 ㅠㅠ
이 블로그는 광고 클릭 수익으로 운영됩니다!
괜찮으시다면 광고 차단을 풀어주세요 ㅠㅠ

안녕하세요. 이번 시간에는 몽구스프로미스(promise)를 사용하는 방법에 대해 알아보겠습니다.

기본적으로 몽고DB(노드용 드라이버)는 콜백으로 결과값을 반환합니다. 콜백은 간단하지만, 다들 아시다시피 중첩되었을 경우 콜백 지옥이 발생할 수 있다는 문제점이 있습니다.

따라서 콜백 대신 프로미스를 보통 많이 사용합니다. 게다가 프로미스는 자바스크립트와 노드에서 비동기 API로 밀어주고 있기 때문에 유망합니다. ES2017에서 나온 async/await도 사용할 수 있기 때문에 익숙해진다면 콜백보다 훨씬 가독성이 좋습니다.

몽고DB에 프로미스를 적용하기 위한 많은 라이브러리들이 있습니다. 몽구스도 역시 프로미스를 지원합니다. 자체적으로도 mPromise라는 것을 가지고 있지만 deprecated되었기 때문에 ES2015의 promise를 사용하는 게 좋습니다.

mongoose.Promise = global.Promise;
mongoose.connect(db, {
  keepAlive: 300000,
  connectTimeoutMS: 30000,
}, (err) => {
  if (err) {
    console.log(`===>  Error connecting to ${db}`);
    console.log(`Reason: ${err}`);
  } else {
    console.log(`===>  Succeeded in connecting to ${db}`);
  }
});

첫 줄을 보시면 DB 연결 전에 global.Promisemongoose.Promise에 대입했습니다. 기본 프로미스를(mPromise) 노드의 프로미스로 교체한 것입니다. bluebird나 q같은 라이브러리로 프로미스를 사용하고 싶다면 그것을 넣어도 됩니다.

이제 코드를 다음과 같이 사용하면 됩니다. 다음과 같은 콜백 함수들이 있을 때

Users.findOne({ name: 'zerocho' }, (err, result) => {
  if (err) {
    throw err;
  }
  Users.update({ name: result.name }, {
    updated: true,
  }, (err, updateResult) => {
    if (err) {
      throw err;
    }
    console.log(updateResult);
  });
});

프로미스로 바꾸면 아래와 같습니다.

Users.findOne({ name: 'zerocho' })
  .then((result) => {
    return Users.update({ name: result.name }, { updated: true });
  })
  .then((updatedResult) => {
    console.log(updatedResult);
  })
  .catch((err) => {
    console.error(err);
  });

프로미스의 장점(코드 중첩 완화, 조건부 쿼리, 에러 한 번에 처리 등)들을 모두 이용할 수 있기 때문에 편리합니다.

첫 줄 User.findOne({ name: 'zerocho' })는 쿼리입니다. 몽구스 는 4 버전부터 쿼리가 프로미스를 지원합니다. 3버전까지는 쿼리를 프로미스로 만들기 위해서 뒤에 exec()을 붙여주어야 했습니다. 4버전부터는 필요하지 않습니다.

const newUser = new Users({
  name: 'zerocho',
  updated: false,
});
newUser.save()
  .then((savedUser) => {
    console.log(savedUser);
  })
  .catch((err) => {
    console.error(err);
  });

객체를 생성하는 메소드인 save()도 자체적으로 promise입니다. (save는 3버전에서도 exec을 붙이지 않습니다)

나중에 async/await으로 전환도 가능합니다.

try {
  const result =  await Users.findOne({ name: 'zerocho' });
  const updatedResult = await Users.update({ name: result.name });
  console.log(updatedResult);
} catch (err) {
  console.error(err);
}

프로미스를 사용했을 때 장점을 소개해보겠습니다. 코드 중첩 완화와 에러 한 번에 처리하는 것은 위의 예제에서도 쉽게 파악할 수 있습니다. 조건부 쿼리할 때 매우 편리한데요.

let promise;
if (req.isAuthenticated()) { // 로그인 상태면
  promise = Users.findOne({ name: req.user._id }); // 내 정보 조회
} else { // 아니면
  promise = Users.find({}); // 전체 조회
}
promise.then(...).catch(...);

이런 식으로 활용이 가능합니다. Promise가 변수에 담기기 때문에 가능한 방식입니다. then 안의 return에서도 분기 처리가 가능합니다.

promise.then(() => {
  return req.isAuthenticated() ? Users.findOne(...) : Users.find({});
});

프로미스만 잘 활용해도 몽고DB 비동기 때문에 발생하는 문제를 어느 정도 극복할 수 있습니다! 다음 시간에는 몽구스 쿼리 빌더에 대해 알아보겠습니다.

목록
투표로 게시글에 관해 피드백을 해주시면 많은 도움이 됩니다. 오류가 있다면 어떤 부분에 오류가 있는지도 알려주세요! 잘못된 정보가 퍼져나가지 않도록 도와주세요.
Copyright © 2016- 무단 전재 및 재배포 금지

댓글

2개의 댓글이 있습니다.
8달 전
조건부 쿼리 시에는 프로미스가 아닌 쿼리 조건 오브젝트를 조건에 맞게 변경하는 게 더 낫지 않을까요?
8달 전
그러셔도 됩니다! 그런데 아예 findOne이랑 find처럼 메소드가 달라져버리는 경우도 있어서요
일 년 전
Users.findOne({ name: 'zerocho' })
.then((result) => {
return Users.update({ name: result.name });
})
.then((updatedResult) => {
console.log(updatedResult);
})
.catch((err) => {
console.error(err);
});

예제에서 만약 result가 없으면 이를 처리하는 catch를 따로 만들고 싶은데 이런건 어떻게 하나요?
일 년 전
if (!result) throw new Error('결과 없음')
처럼 하시면 됩니다.