게시글

강좌15 - MongoDB - 3년 전 등록 / 5달 전 수정

Mongoose(몽구스) 프로미스

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

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

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

몽고DB에 프로미스를 적용하기 위한 많은 라이브러리들이 있습니다. 몽구스도 역시 프로미스를 지원합니다. ES2015의 promise를 사용하지만 다른 프로미스(bluebird같은)로 바꿀 수도 있습니다.

mongoose.Promise = require('bluebird');
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 연결 전에 bluebirdmongoose.Promise에 대입했습니다. 기본 프로미스를(ES2015) 블루버드의 프로미스로 교체한 것입니다. 첫 줄을 넣지 않으면 그냥 ES2015 프로미스를 사용합니다. 특별한 이유가 있지 않다면 그냥 ES2015 프로미스를 쓰시는 게 좋습니다.

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

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' }).exec()
  .then((result) => {
    return Users.update({ name: result.name }, { updated: true }).exec();
  })
  .then((updatedResult) => {
    console.log(updatedResult);
  })
  .catch((err) => {
    console.error(err);
  });

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

첫 줄 User.findOne({ name: 'zerocho' })는 쿼리입니다. 몽구스 4버전부터 쿼리가 then을 지원합니다. 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는 exec을 붙이지 않습니다)

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

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

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

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

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

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

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

조회수:
0
목록
투표로 게시글에 관해 피드백을 해주시면 게시글 수정 시 반영됩니다. 오류가 있다면 어떤 부분에 오류가 있는지도 알려주세요! 잘못된 정보가 퍼져나가지 않도록 도와주세요.
Copyright 2016- . 무단 전재 및 재배포 금지. 출처 표기 시 인용 가능.

댓글

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

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