中间件(pre 和 post 钩子)是在异步函数执行时函数传入的控制函数。
Mongoose 4.x 有四种中间件:
对于 document 中间件,this 指向当前 document。
Document 中间件支持以下 document 操作:
initvalidatesaveremove对于 query 中间件,this 指向当前 query。
Query 中间件支持以下 Model 和 Query 操作:
countfindfindOnefindOneAndRemovefindOneAndUpdateupdateAggregate 中间件作用于 MyModel.aggregate(),它会在你对 aggregate 对象调用 exec() 执行。对于 aggregate 中间件,this 指向当前 aggregation 对象。
aggregate对于 Model 中间件,this 指向当前 model。
Model 中间件支持以下 Model 操作:
insertMany所有中间件支持 pre 和 post 钩子。
⚠️ 注意:Query 是没有 remove() 钩子的,只有 document 有,如果你设定了 remove 钩子,他将会在你调用 myDoc.remove()(而不是 MyModel.remove())时触发。
pre 钩子分串行和并行两种。
串行中间件是一个接一个地执行。
具体来说,上一个中间件调用 next 函数时,下一个执行。
const schema = new Schema(..);schema.pre('save', function(next){// do stuffnext();})
next() 不会阻止剩余代码运行。你可以使用提早 return 模式阻止 next() 后面的代码运行。
const schema = new Schema(..);schema.pre('save', function(next){if (foo()){console.log('calling next!')// `return next()` will make sure the rest of this function does't run/* return */next();}// Unless you comment out the `return` above, 'after next' will printconsole.log('after next');})
并行中间件提供细粒度流控制。
const 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 的时候才会执行。
中间件对原子化模型逻辑很有帮助。这里有一些其它建议:
如果 pre 钩子出错,mongoose 将不会执行后面的函数。Mongoose 会向回调函数传入 err 参数,或者 reject 返回的 Promise。
🌰 示例:
schema.pre('save', funciton(next){const err = new Error('something went wrong');// If you call `next()` with an argument, that argument is assumed to benext(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 return a promise that rejectsthrow new Error('something went wrong')})schema.pre('save', 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 becasue a pre hook errored outmyDoc.save(function(err){console.log(err.message);// something went wrong})
多次调用 next() 是无效的。如果你调用 next() 带有错误参数 err1,然后你再抛一个 err2,mongoose 只会传递 err1。
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);});
如果你给回调函数传入两个参数,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();});