# 中間件(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的方法。