> For the complete documentation index, see [llms.txt](https://mongoose.shujuwajue.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://mongoose.shujuwajue.com/guide/middleware.md).

# 中间件（middleware）

> 原文：[Middleware](http://mongoosejs.com/docs/middleware.htm)\
> 翻译：小虾米（QQ:509129）

## 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`。

```javascript
var schema = new Schema(..);
schema.pre('save', function(next) {
  // do stuff
  next();
});
```

#### Parallel （并行）

并行中间件提供了更多的细粒度的流量控制。

```javascript
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`一个类型错误的参数，则流被中断，并且将错误传递给回调。

```javascript
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`回调函数传递给它的。后置钩子是是一种来为这些方法注册传统事件侦听器方式。

```javascript
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()`函数，以触发序列中的下一个中间件。

```javascript
// 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')`钩子之前被调用到。

```javascript
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有这些功能不同的钩。

```javascript
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`的时间戳，你可以使用以下的前置。

```javascript
schema.pre('update', function() {
  this.update({},{ $set: { updatedAt: new Date() } });
});
```

### 下一步

现在我们已经掌握了中间件，让我们去加入它的查询[人口](http://mongoosejs.com/docs/populate.html)助手一看Mongoose的方法。
