- 英文原版
- 关于中文文档
- 快速上手
- 模式(Schemas)
- 模式类型(SchemaTypes)
- 连接(Connections)
- 模型(Models)
- 文档 (Documents)
- 子文档(Subdocuments)
- 查询 (queries)
- 验证 (validation)
- 中间件 (middleware)
- 填充 (Populate)
- 鉴别器 (Discriminators)
- 插件
- AWS Lambda
- API 文档
- Schema
- Connection
- Document
- Model
- Query
- Aggregate
- SchemaType
- VirtualType
- Error
- Version Compatibility
- FAQ
Middleware
中间件 (pre 和 post 钩子) 是在异步函数执行时函数传入的控制函数。 Middleware is specified on the schema 级别,在写插件的时候很有用。 Mongoose 4.x 有四种中间件: document 中间件,model 中间件,aggregate 中间件,和 query 中间件。 对于 document 中间件, this 指向当前 document。 Document 中间件支持以下 document 操作:
对于 query 中间件,this 指向当前 query。 Query 中间件支持以下 Model 和 Query 操作:
Aggregate 中间件作用于 MyModel.aggregate(), 它会在你对 aggregate 对象调用 exec() 时执行。 对于 aggregate 中间件,this 指向当前aggregation 对象。
对于 model 中间件,this 指向当前 model。 Model 中间件支持以下 Model 操作:
所有中间件支持 pre 和 post 钩子, 下面将详细解释这两个钩子的细节。
注意: Query 是没有 remove() 钩子的,只有 document 有, 如果你设定了 'remove' 钩子,他将会在你调用 myDoc.remove()(而不是 MyModel.remove())时触发。 注意: create() 函数会触发 save() 钩子.
Pre
pre 钩子分串行和并行两种。
串行
串行中间件一个接一个地执行。具体来说, 上一个中间件调用 next 函数的时候,下一个执行。
var schema = new Schema(..);schema.pre('save', function(next) {// do stuffnext();});
在mongoose 5.x中, 除了手动调用 next(), 你还可以返回一个promise, 甚至是 async/await。
schema.pre('save', function() {return doStuff().then(() => doMoreStuff());});// Or, in Node.js >= 7.6.0:schema.pre('save', async function() {await doStuff();await doMoreStuff();});
next() 不会 阻止剩余代码的运行。 你可以使用 提早 return 模式 阻止 next() 后面的代码运行。
var schema = new Schema(..);schema.pre('save', function(next) {if (foo()) {console.log('calling next!');// `return next();` will make sure the rest of this function doesn't run/*return*/ next();}// Unless you comment out the `return` above, 'after next' will printconsole.log('after next');});
并行
并行中间件提供细粒度流控制。
var schema = new Schema(..);// `true` means this is a parallel middleware. You **must** specify `true`// as the second parameter if you want to use parallel middleware.schema.pre('save', true, function(next, done) {// calling next kicks off the next middleware in parallelnext();setTimeout(done, 100);});
在这个例子里,save 方法将在所有中间件都调用了 done 的时候才会执行。
使用场景
中间件对原子化模型逻辑很有帮助。这里有一些其他建议:
- 复杂的数据校验
- 删除依赖文档(删除用户后删除他的所有文章)
- asynchronous defaults
- asynchronous tasks that a certain action triggers
错误处理
如果 pre 钩子出错,mongoose 将不会执行后面的函数。 Mongoose 会向回调函数传入 err 参数, 或者 reject 返回的 promise。 这里列举几个错误处理的方法:
schema.pre('save', function(next) {const err = new Error('something went wrong');// If you call `next()` with an argument, that argument is assumed to be// an error.next(err);});schema.pre('save', function() {// You can also return a promise that rejectsreturn new Promise((resolve, reject) => {reject(new Error('something went wrong'));});});schema.pre('save', function() {// You can also throw a synchronous errorthrow new Error('something went wrong');});schema.pre('save', async function() {await Promise.resolve();// You can also throw an error in an `async` functionthrow new Error('something went wrong');});// later...// Changes will not be persisted to MongoDB because a pre hook errored outmyDoc.save(function(err) {console.log(err.message); // something went wrong});
多次调用 next() 是无效的。如果你调用 next() 带有错误参数 err1, 然后你再抛一个 err2,mongoose 只会传递 err1。
Post 中间件
post 中间件在方法执行之后 调用,这个时候每个 pre 中间件都已经完成。
schema.post('init', function(doc) {console.log('%s has been initialized from the db', doc._id);});schema.post('validate', function(doc) {console.log('%s has been validated (but not saved yet)', doc._id);});schema.post('save', function(doc) {console.log('%s has been saved', doc._id);});schema.post('remove', function(doc) {console.log('%s has been removed', doc._id);});
异步 Post 钩子
如果你给回调函数传入两个参数,mongoose 会认为第二个参数是 next() 函数,你可以通过 next 触发下一个中间件
// Takes 2 parameters: this is an asynchronous post hookschema.post('save', function(doc, next) {setTimeout(function() {console.log('post1');// Kick off the second post hooknext();}, 10);});// Will not execute until the first middleware calls `next()`schema.post('save', function(doc, next) {console.log('post2');next();});
Save/Validate 钩子
save() 函数触发 validate() 钩子,mongoose validate() 其实就是 pre('save') 钩子, 这意味着所有 pre('validate') 和 post('validate') 都会在 pre('save') 钩子之前调用。
schema.pre('validate', function() {console.log('this gets printed first');});schema.post('validate', function() {console.log('this gets printed second');});schema.pre('save', function() {console.log('this gets printed third');});schema.post('save', function() {console.log('this gets printed fourth');});
findAndUpdate() 与 Query 中间件使用注意
pre 和 post save() 钩子都不执行于 update(),findOneAndUpdate() 等情况。 你可以在此了解更多细节。 Mongoose 4.0 为这些函数制定了新钩子。
schema.pre('find', function() {console.log(this instanceof mongoose.Query); // truethis.start = Date.now();});schema.post('find', function(result) {console.log(this instanceof mongoose.Query); // true// prints returned documentsconsole.log('find() returned ' + JSON.stringify(result));// prints number of milliseconds the query tookconsole.log('find() took ' + (Date.now() - this.start) + ' millis');});
Query 中间件 不同于 document 中间件:document 中间件中, this 指向被更新 document,query 中间件中, this 指向 query 对象而不是被更新 document。
例如,如果你要在每次 update之前更新 updatedAt 时间戳, 你可以使用 pre 钩子。
schema.pre('update', function() {this.update({},{ $set: { updatedAt: new Date() } });});
错误处理中间件
4.5.0 新增
next() 执行错误时,中间件执行立即停止。但是我们有特殊的 post 中间件技巧处理这个问题 —— 错误处理中渐渐,它可以在出错后执行你指定的代码。
错误处理中间件比普通中间件多一个 error 参数,并且 err 作为第一个参数传入。 而后错误处理中间件可以让你自由地做错误的后续处理。
var schema = new Schema({name: {type: String,// Will trigger a MongoError with code 11000 when// you save a duplicateunique: true}});// 处理函数**必须**传入 3 个参数: 发生的错误// 返回的文件,以及 next 函数schema.post('save', function(error, doc, next) {if (error.name === 'MongoError' && error.code === 11000) {next(new Error('There was a duplicate key error'));} else {next(error);}});// Will trigger the `post('save')` error handlerPerson.create([{ name: 'Axl Rose' }, { name: 'Axl Rose' }]);
对于 query 中间件也可以使用错误处理。你可以定义一个 post update() 钩子, 它可以捕获 MongoDB 重复 key 错误。
// The same E11000 error can occur when you call `update()`// This function **must** take 3 parameters. If you use the// `passRawResult` function, this function **must** take 4// parametersschema.post('update', function(error, res, next) {if (error.name === 'MongoError' && error.code === 11000) {next(new Error('There was a duplicate key error'));} else {next(error);}});var people = [{ name: 'Axl Rose' }, { name: 'Slash' }];Person.create(people, function(error) {Person.update({ name: 'Slash' }, { $set: { name: 'Axl Rose' } }, function(error) {// `error.message` will be "There was a duplicate key error"});});
下一步
我们了解了中间件,接着我们看看 Mongoose 怎么用 population 模拟 JOIN 操作。
mongoose