안녕하세요. 이번 시간에는 몽구스로 프로미스(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 연결 전에 bluebird
를 mongoose.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 비동기 때문에 발생하는 문제를 어느 정도 극복할 수 있습니다! 다음 시간에는 몽구스 쿼리 빌더에 대해 알아보겠습니다.