Подтвердить что ты не робот

Как работать с асинхронным кодом в виртуальных свойствах Mongoose?

Я пытаюсь работать с привязкой документов в разных коллекциях (не вложенных документах), а пока проблема для этого в Mongooose, я пытаюсь обходите его теперь ленивой загрузкой связанного документа с виртуальным свойством, как документировано на веб-сайте Mongoose.

Проблема заключается в том, что getter для виртуального принимает функцию в качестве аргумента и использует возвращаемое значение для виртуального свойства. Это замечательно, когда виртуальный сервер не требует каких-либо асинхронных вызовов для вычисления его значения, но не работает, когда мне нужно сделать асинхронный вызов для загрузки другого документа. Здесь пример кода, с которым я работаю:

TransactionSchema.virtual('notebook')
  .get( function() { // <-- the return value of this function is used as the property value
    Notebook.findById(this.notebookId, function(err, notebook) {
      return notebook; // I can't use this value, since the outer function returns before we get to this code
    })
    // undefined is returned here as the properties value
  });

Это не работает, так как функция возвращается до завершения асинхронного вызова. Есть ли способ, которым я мог бы использовать библиотеку управления потоками, чтобы выполнить эту работу, или я могу изменить первую функцию, чтобы передать вызов findById на getter вместо анонимной функции?

4b9b3361

Ответ 1

Вы можете определить виртуальный метод, для которого вы можете определить обратный вызов.

Используя ваш пример:

TransactionSchema.method('getNotebook', function(cb) {
  Notebook.findById(this.notebookId, function(err, notebook) {
    cb(notebook);
  })
});

И хотя единственный комментатор, кажется, один из тех педантичных типов, вы также не должны бояться встраивания документов. Его один из монгольских сильных сторон от того, что я понимаю.

Один использует приведенный выше код так:

instance.getNotebook(function(nootebook){
    // hey man, I have my notebook and stuff
});

Ответ 2

В то время как это касается более широкой проблемы, а не конкретного вопроса, я все же думал, что стоит представить:

Вы можете легко загрузить связанный документ из другой коллекции (имеющий почти идентичный результат как определение виртуального) с помощью функции Mongoose query populate. Используя приведенный выше пример, для этого требуется указать ref ObjectID в схеме Transaction (указать на коллекцию Notebook), а затем вызвать populate(NotebookId) при построении запроса. Связанная документация Mongoose достаточно подробно описывает это.

Я не знаком с историей Mongoose, но я предполагаю, что populate не существовало, когда эти более ранние ответы были отправлены.

Ответ 3

Подход Josh отлично подходит для поиска одного документа, но моя ситуация была немного сложнее. Мне нужно было посмотреть на вложенное свойство для целого массива объектов. Например, моя модель больше походила на это:

var TransactionSchema = new Schema({
  ...
  , notebooks: {type: [Notebook]}
});

var NotebookSchema = new Schema({
  ...
  , authorName: String  // this should not necessarily persist to db because it may get stale
  , authorId: String
});

var AuthorSchema = new Schema({
  firstName: String
  , lastName: String
});

Затем, в моем коде приложения (я использую Express), когда я получаю транзакцию, я хочу, чтобы все записные книжки с фамилией автора:

...
TransactionSchema.findById(someTransactionId, function(err, trans) {
  ...
  if (trans) {
    var authorIds = trans.notebooks.map(function(tx) {
        return notebook.authorId;
      });

    Author.find({_id: {$in: authorIds}, [], function(err2, authors) {
      for (var a in authors) {
        for (var n in trans.notebooks {
          if (authors[a].id == trans.notebooks[n].authorId) {
            trans.notebooks[n].authorLastName = authors[a].lastName;
            break;
          }
        }
      }
      ...
    });

Это кажется дико неэффективным и взломанным, но я не мог понять другого способа добиться этого. Наконец, я новичок в node.js, mongoose и stackoverflow, поэтому простите меня, если это не самое подходящее место для расширения этого обсуждения. Это просто, что решение Josh было самым полезным в моем конечном "решении".

Ответ 4

Поскольку это старый вопрос, я решил, что он может использовать обновление.

Для достижения асинхронных виртуальных полей вы можете использовать mongoose-fill, как указано в mongoose github issue: https://github.com/Automattic/mongoose/issues/1894