NodeJS环境下使用MongoDB时,作为数据库管理工具的Mongoose已经成为项目开发的标配,它丰富了Mongo的原生API,为开发者提供了扩展接口,同时以 schema-based 的方式,来操作Mongo

在开发Node时读写数据库难免要进行频繁且深层次的回调操作好在Mongoose的读写操作都有方法以 promise 对象返回,基于这点,结合其它三方Promise类库,可以避免多重嵌套的发生。(ES6 Generator 能更好的控制异步流程,结合Promise可以更好的解决 Callback Hell)

Talk is cheap. Show me the code

先建立我们的数据模型 —— Family

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var FamilySchema = new mongoose.Schema({
generation: {
type: Number,
default: 1
},
sex: {
type: String,
default: 'male'
},
name: {
type: String,
default: ''
}
});

var Family = mongoose.model('Family', FamilySchema);

Mongsoose 的Model层API 大致分为:创建(Create)、更新(Update)、读取(Find)和删除(Remove)四类,createremove 操作返回 promise 对象,updatefind 操作返回 query 对象,需要注意的是,使用 find前缀 方法(如:Model.findOneAndRemove、Model.findByIdAndRemove、Model.findByIdAndUpdate)时返回对象均为 query

对于 query 对象,只需要对其调用 exec 方法,就可以返回 promise 对象了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* Query
*/

Family.findOne({name: '转二'}).exec(); // Return promise

/**
* Update
*/

Family.findByIdAndUpdate(id, {$set: {
name: '转二',
generation: 3
}}).exec(); // Return promise

/**
* Create
*/

var array = [{
generation: 4,
sex: female,
name: '虫虫'
}, {
generation: 3,
sex: female,
name: '芮芮'
}];

Family.create(array); // Return promise

/**
* Delete
*/


Family.remove({ name: '转二' }); // Return promise

有了 promise 返回,异步梦魇已经解决一半了

promise 类库中我比较喜欢Q,相比于其他同类工具,Q比较简单,而且和 Angular$qjQueryDeferred 语法相似。不过还是推荐使用 es6-promise,作为 polyfill 可以让代码在未来无缝衔接 es6

为了举例子,现在创建第二个数据模型 —— Company

1
2
3
4
5
6
7
8
9
10
var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var CompanyMemberSchema = new mongoose.Schema({
empName: String,
empId: String,
sex: String
});

var CompanyMember = mongoose.model('CompanyMember', CompanyMemberSchema);

我们想要从一个 Family 中通过 _id 找到一个家庭成员,通过这个成员的名字,在某个确定的公司里查找此人,然后把此人从公司名单中移除

Without promise

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var id = "1237213921936129";

Family.find({_id: id}, function(err, persons) {
CompanyMember.findOne({
'empName': persons[0]['name']
}, function(err, person) {
CompanyMember.remove({
'empId': person.empId
}, function(err, person) {
if(err) {
return console.error(err);
}

console.log('Remove' + person + 'successfully!');
})
})
});

With promise (Q)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
var id = "1237213921936129";

Q.fcall(function() {

return Family.findOne({
_id: id
}).exec();
// 返回 promise
}).then(function(person) {

return CompanyMember.findOne({
empName: person['name']
}).exec();
// 返回 promise
}).then(function(person) {

return Company.remove({empId: person['empId']});
// 返回 promise
}).then(function(person) {

console.log('Remove' + person + 'successfully!');

}).catch(function (err) {
// 捕捉错误
return console.error(err);

}).done();
// 结束链式调用

至于并发控制,Q 也有解决方案:

1
2
3
4
5
6
Q.spread([promiseA, promiseB], function(A_value, B_value) {
console.log('A_value': A_value);
console.log('B_value': B_value);
}, funcion(err) {
return console.error(err);
});

Q的使用查阅 http://documentup.com/kriskowal/q/
Mongoose的使用查阅 http://mongoosejs.com/docs/guide.html

下一篇会聊一聊 Generator + Promise 组合来解决 Mongoose 的异步回调问题

本文地址: https://mrpeak.github.io/2015/03/20/mongoose-promise/