# 中间件(middleware)
> 原文:[Middleware](http://mongoosejs.com/docs/middleware.htm)
## Middleware
中间件(也称为前置和后置钩子)是异步函数执行过程中传递的控制的函数。中间件是在schema级别上指定的,并用于编写[插件](http://mongoosejs.com/docs/plugins.html)是非常有用的。Mongoose 4.0 有2种类型的中间件:文档(document)中间件和查询(query)中间件。文档(document)中间件支持以下文档方法。
- [init](http://mongoosejs.com/docs/api.html#document_Document-init)
- [validate](http://mongoosejs.com/docs/api.html#document_Document-validate)
- [save](http://mongoosejs.com/docs/api.html#model_Model-save)
- [remove](http://mongoosejs.com/docs/api.html#model_Model-remove)
查询(query)中间件支持一下模型和查询方法。
- [count](http://mongoosejs.com/docs/api.html#query_Query-count)
- [find](http://mongoosejs.com/docs/api.html#query_Query-find)
- [findOne](http://mongoosejs.com/docs/api.html#query_Query-findOne)
- [findOneAndRemove](http://mongoosejs.com/docs/api.html#query_Query-findOneAndRemove)
- [findOneAndUpdate](http://mongoosejs.com/docs/api.html#query_Query-findOneAndUpdate)
- [update](http://mongoosejs.com/docs/api.html#query_Query-update)
文档(document)中间件和查询(query)中间件支持前置和后置钩子。前置和后置钩子如何工作更详细的描述如下。
**注**:这里没有查询的`remove()`钩子,只对文档。如果你设定了一个 'remove'钩子,它将解雇当你调用`myDoc`时。`remove()`,不是当你调用 `MyModel.remove()`。
### Pre (前置钩子)
有两种类型的前置钩子,串行(serial)和并行(parallel)。
#### Serial (串行)
串行中间件是一个接一个的执行,当每个中间件调用`next`。
```
var schema = new Schema(..);
schema.pre('save', function(next) {
// do stuff
next();
});
```
#### Parallel (并行)
并行中间件提供了更多的细粒度的流量控制。
```
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 parallel
next();
setTimeout(done, 100);
});
```
钩子方法,在这种情况下,保存,将不会被执行,直到完成每个中间件。
#### 使用案例
中间件用于雾化模型逻辑和避免嵌套异步代码块。这里有一些其他的想法:
complex validation removing dependent documents (removing a user removes all his blogposts) asynchronous defaults asynchronous tasks that a certain action triggers triggering custom events notifications
- 杂的验证
- 删除相关文件
- (删除一个用户删除了他所有的博客文章)
- 异步默认
- 异步任务,某些动作触发器
- 引发自定义事件
- 通知
#### 错误处理
如果任何中间件调用`next`或`done`一个类型错误的参数,则流被中断,并且将错误传递给回调。
```
schema.pre('save', function(next) {
// You **must** do `new Error()`. `next('something went wrong')` will
// **not** work
var err = new Error('something went wrong');
next(err);
});
// later...
myDoc.save(function(err) {
console.log(err.message) // something went wrong
});
```
### 后置中间件(Post middleware)
[后置](http://mongoosejs.com/docs/api.html#schema_Schema-post)中间件被执行后,钩子的方法和所有的前置中间件已经完成。后置的中间件不直接接收的流量控制, 如: 没有 `next` 或 `done`回调函数传递给它的。后置钩子是是一种来为这些方法注册传统事件侦听器方式。
```
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);
});
```
### 异步后置钩子
虽然后中间件不接收流量控制,但您仍然可以确保异步后置钩子在预定义的命令中执行。如果你的后置钩子函数至少需要2个参数,mongoose将承担第二个参数是一个`next()`函数,以触发序列中的下一个中间件。
```
// Takes 2 parameters: this is an asynchronous post hook
schema.post('save', function(doc, next) {
setTimeout(function() {
console.log('post1');
// Kick off the second post hook
next();
}, 10);
});
// Will not execute until the first middleware calls `next()`
schema.post('save', function(doc, next) {
console.log('post2');
next();
});
```
### 保存/验证钩子
`save()`函数触发`validate()`钩子,因为 mongoose 有一个内置的`pre('save')`钩子叫`validate()`。这意味着所有的`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() and 插件中间件
前置和后置的 `save()` 钩子不能执行在`update()`,`findOneAndUpdate()`, 等.。你可以看到一个更详细的讨论这个问题为什么在[GitHub](http://github.com/Automattic/mongoose/issues/964)。Mongoose 4.0有这些功能不同的钩。
```
schema.pre('find', function() {
console.log(this instanceof mongoose.Query); // true
this.start = Date.now();
});
schema.post('find', function(result) {
console.log(this instanceof mongoose.Query); // true
// prints returned documents
console.log('find() returned ' + JSON.stringify(result));
// prints number of milliseconds the query took
console.log('find() took ' + (Date.now() - this.start) + ' millis');
});
```
查询中间件不同于文档中间件,在一个微妙但重要的方式:在文档中间件中,这是指被更新的文档。在查询中间件,mongoose并不一定参考被更新的文档,所以这是指查询的对象而不是被更新的文档。
例如,如果你想在每次调用`update()`时添加一个`updatedAt`的时间戳,你可以使用以下的前置。
```
schema.pre('update', function() {
this.update({},{ $set: { updatedAt: new Date() } });
});
```
### 下一步
现在我们已经掌握了中间件,让我们去加入它的查询[人口](http://mongoosejs.com/docs/populate.html)助手一看Mongoose的方法。