Среднесвязующий слой
Среднесвязующий слой (также называемый пре- и пост-хуками) — это функции, которым передаётся управление во время выполнения асинхронных функций. Среднесвязующий слой задаётся на уровне схемы и полезен для написания плагинов. Mongoose 4.x имеет 4 типа среднесвязующего слоя: среднесвязующий слой документа, среднесвязующий слой модели, среднесвязующий слой агрегации и среднесвязующий слой запроса. Среднесвязующий слой документа поддерживается для следующих функций документа. В функциях среднесвязующего слоя документа this относится к документу.
Среднесвязующий слой запроса поддерживается для следующих функций модели и запроса. В функциях среднесвязующего слоя запроса this относится к запросу.
Среднесвязующий слой агрегации предназначен для MyModel.aggregate(). Среднесвязующий слой агрегации выполняется при вызове exec() на объекте агрегации. В среднесвязующем слое агрегации this относится к объекту агрегации.
Среднесвязующий слой модели поддерживается для следующих функций модели. В функциях среднесвязующего слоя модели this относится к модели.
Все типы среднесвязующего слоя поддерживают пре- и пост-хуки. Более подробное описание работы пре- и пост-хуков приведено ниже.
Примечание: нет хука запроса для remove(), только для документов. Если вы установите хук «удаление», он будет срабатывать при вызове myDoc.remove(), а не при вызове MyModel.remove(). Примечание: функция create() вызывает хуки save().
Пре
Существует два типа pre хуков: последовательный и параллельный.
Последовательный
Последовательные функции среднесвязующего слоя выполняются одна за другой, когда каждый среднесвязующий слой вызывает next.
var schema = new Schema(..); schema.pre('save', function(next) { // do stuff next(); });
Вызов next() не останавливает выполнение остальной части кода в вашей функции среднесвязующего слоя. Используйте шаблон раннего return, чтобы предотвратить выполнение остальной части вашей функции среднесвязующего слоя при вызове next().
var schema = new Schema(..); schema.pre('save', function(next) { if (foo()) { console.log('calling next!'); // `return next();` will make sure the rest of this function doesn't run /*return*/ next(); } // Unless you comment out the `return` above, 'after next' will print console.log('after next'); });
Параллельный
Параллельные среднесвязующие слои предлагают более точный контроль потока.
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); });
Связанный метод, в данном случае save, не будет выполнен, пока done не будет вызван каждым среднесвязующим слоем.
Примеры использования
Среднесвязующие слои полезны для разбивки логики модели на атомарные части и избежания вложенных блоков асинхронного кода. Вот ещё несколько идей:
- сложная валидация
- удаление зависимых документов
- (удаление пользователя удаляет все его посты)
- асинхронные значения по умолчанию
- асинхронные задачи, которые запускаются определённым действием
- триггеринг пользовательских событий
- уведомления
Обработка ошибок
Если любой среднесвязующий слой вызывает next или done с параметром типа Error, поток прерывается, а ошибка передаётся в обратный вызов.
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 });
Пост-среднесвязующий слой
пост-среднесвязующий слой выполняется после связанного метода и всех его pre среднесвязующих слоёв. post среднесвязующие слои не получают непосредственного управления потоком, например, им не передаются обратные вызовы next или done. post хуки — это способ регистрации традиционных обработчиков событий для этих методов.
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() и среднесвязующему слою запроса
Пре- и пост-хуки save() не выполняются для update(), findOneAndUpdate() и т. д. Более подробное обсуждение, почему, можно найти в этом вопросе на GitHub. 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'); });
Среднесвязующий слой запроса отличается от среднесвязующего слоя документа тонким, но важным аспектом: в среднесвязующем слое документа this относится к обновляемому документу. В среднесвязующем слое запроса mongoose не обязательно имеет ссылку на обновляемый документ, поэтому this относится к объекту запроса, а не к обновляемому документу.
Например, если вы хотите добавить отметку времени updatedAt к каждому вызову update(), вы используете следующий пре-хук.
schema.pre('update', function() { this.update({},{ $set: { updatedAt: new Date() } }); });
Обработка ошибок в среднесвязующем слое
Новое в 4.5.0
Выполнение среднесвязующего слоя обычно останавливается в первый раз, когда какой-либо среднесвязующий слой вызывает next() с ошибкой. Однако существует особый вид пост-среднесвязующего слоя, называемый «среднесвязующим слоем обработки ошибок», который выполняется именно при возникновении ошибки.
Среднесвязующий слой обработки ошибок определяется как среднесвязующий слой, принимающий один дополнительный параметр: «ошибку», возникшую в качестве первого параметра функции. Среднесвязующий слой обработки ошибок затем может преобразовать ошибку по вашему желанию.
var schema = new Schema({ name: { type: String, // Will trigger a MongoError with code 11000 when // you save a duplicate unique: true } }); // Handler **must** take 3 parameters: the error that occurred, the document // in question, and the `next()` function schema.post('save', function(error, doc, next) { if (error.name === 'MongoError' && error.code === 11000) { next(new Error('There was a duplicate key error')); } else { next(error); } }); // Will trigger the `post('save')` error handler Person.create([{ name: 'Axl Rose' }, { name: 'Axl Rose' }]);
Среднесвязующий слой обработки ошибок также работает с среднесвязующим слоем запроса. Вы также можете определить пост-хук update() для перехвата ошибок дублирования ключей MongoDB.
// The same E11000 error can occur when you call `update()` // This function **must** take 3 parameters. If you use the // `passRawResult` function, this function **must** take 4 // parameters schema.post('update', function(error, res, next) { if (error.name === 'MongoError' && error.code === 11000) { next(new Error('There was a duplicate key error')); } else { next(error); } }); var people = [{ name: 'Axl Rose' }, { name: 'Slash' }]; Person.create(people, function(error) { Person.update({ name: 'Slash' }, { $set: { name: 'Axl Rose' } }, function(error) { // `error.message` will be "There was a duplicate key error" }); });
Далее
Теперь, когда мы рассмотрели среднесвязующие слои, давайте посмотрим на подход Mongoose к имитации JOIN с помощью своего вспомогательного средства запросов популяции.
© 2010 LearnBoost
Licensed under the MIT License.
https://mongoosejs.com/docs/4.x/docs/middleware.html