# 中间件（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的方法。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://mongoose.shujuwajue.com/guide/middleware.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
