【问题标题】:How to work with async code in Mongoose virtual properties?如何在 Mongoose 虚拟属性中使用异步代码?
【发布时间】:2011-08-29 23:22:53
【问题描述】:

我正在尝试处理不同集合(不是嵌入文档)中的关联文档,虽然 Mongooose 中有一个 issue for that,但我现在正尝试通过延迟加载具有虚拟属性的关联文档来解决它如记录的on the Mongoose website

问题在于虚拟的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 而不是匿名函数?

【问题讨论】:

  • 谢谢,我已经更新了代码示例,希望现在更清楚了。
  • 我看到 Josh 设法给你异步代码,看。
  • 您遇到的问题只是 JS 的“限制”以及 Mongoose 为虚拟同步编写 get 方法的方式。它需要一个返回值的函数,并且没有流控制库能够让您将 async 放入其中并使其按预期工作。如果您尝试在 Underscore/lodash 回调中执行异步操作,您将遇到同样的事情。因此需要 Josh 的解决方案来滚动自己的异步方法并绕过 Mongoose 的同步 get

标签: asynchronous node.js mongoose


【解决方案1】:

由于这是一个老问题,我认为它可能需要更新。

要实现异步虚拟字段,可以使用mongoose-fill,如mongoose的github issue中所述:https://github.com/Automattic/mongoose/issues/1894

【讨论】:

    【解决方案2】:

    虽然这解决了更广泛的问题而不是具体问题,但我仍然认为值得提交:

    您可以使用 Mongoose 的查询 populate 函数轻松地从另一个集合中加载关联文档(与定义虚拟的结果几乎相同)。使用上面的示例,这需要在 Transaction 架构中指定 ObjectID 的引用(以指向 Notebook 集合),然后在构造查询时调用 populate(NotebookId)。链接的 Mongoose 文档非常彻底地解决了这个问题。

    我不熟悉 Mongoose 的历史,但我猜populate 在提交这些早期答案时并不存在。

    【讨论】:

    • imhe 这一直是最适合我的。最容易实现和维护
    【解决方案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;
              }
            }
          }
          ...
        });
    

    这似乎非常低效和hacky,但我想不出另一种方法来实现这一点。最后,我是 node.js、mongoose 和 stackoverflow 的新手,如果这不是扩展这个讨论的最合适的地方,请原谅我。只是 Josh 的解决方案对我最终的“解决方案”最有帮助。

    【讨论】:

    • 您可以制作 {_id: model} 的地图并避免额外的循环。 Object.keys(map) 然后会返回你的 ID。
    【解决方案4】:

    你可以定义一个虚方法,你可以为它定义一个回调。

    使用您的示例:

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

    虽然唯一的评论者似乎是那些迂腐的类型之一,但您也不应该害怕嵌入文档。据我了解,这是 mongos 的强项之一。

    这样使用上面的代码:

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

    【讨论】:

    • 您还可以在本地缓存 Notebook 对象以防止从 DB 中多次获取,例如:TransactionSchema.method('getNotebook', function(cb) { if (this._notebook) return cb(notebook); Notebook.findById(this.notebookId, function(err, notebook) { this._notebook = notebook; cb(notebook); }) });
    • 但是是否可以在虚拟中使用这种模式,以便 TransactionSchema.virtual('notebook').get(function()) 调用方法 'getNotebook' 并返回结果?回调似乎在这种情况下不起作用。
    • @Greg,没错。虽然这是一个很好的问题解决方案,但如果您真正需要的是异步虚拟字段,则并非如此。但我相信异步 getter/setter 在 JS 中是不可能的。
    猜你喜欢
    • 2013-01-30
    • 1970-01-01
    • 2011-12-29
    • 2021-04-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多