본문 바로가기

개발자는 오늘도 달립니다.

[MongoDB] Mongoose(몽구스) 메뉴얼 - 프로미스 편 본문

데이터베이스/몽고DB

[MongoDB] Mongoose(몽구스) 메뉴얼 - 프로미스 편

✍21시간 2020. 7. 10. 09:09

개발 프로그램에서 몽고DB를 핸들링 하기 위해서는 벤더사에서 제공하는 API를 활용해야 합니다. 이번 포스팅은 그 중 몽구스라는 API를 ECMA 5 프로미스(promise)를 사용하는 방법에 대해 알아보겠습니다.

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

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

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

mongoose.Promise = require("bluebird");

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

첫 줄을 보시면 DB 연결 전에 bluebird를 mongoose.Promise에 대입했습니다. 기본 프로미스를(ES2015) 블루버드의 프로미스로 교체한 것입니다. 첫 줄을 넣지 않으면 그냥 ES2015 프로미스를 사용합니다. 특별한 이유가 있지 않다면 그냥 ES2015 프로미스를 쓰시는 게 좋습니다.

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

Users.findOne({ name: "zerocho" }, (errresult=> {
  if (err) {
    throw err;
  }

  Users.update(
    { name: result.name },
    { updated: true },
    (errupdateResult=> {
      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 비동기 때문에 발생하는 문제를 어느 정도 극복할 수 있습니다!

Comments