(mongoose/promises)如何检查文档是否是使用带有upsert的findOneAndUpdate创建的

Mat*_*Way 5 mongoose mongodb node.js promise mongodb-query

考虑这段代码,我需要创建或更新特定文档.

Inbox.model.findOneAndUpdate({ number: req.phone.number }, {
    number: req.phone.number,
    country: req.phone.country,
    token: hat(),
    appInstalled: true
}, { new: true, upsert: true }).then(function(inbox){
    /*
       do something here with inbox, but only if the inbox was created (not updated)
    */
});
Run Code Online (Sandbox Code Playgroud)

mongoose是否有能力区分是否创建或更新了文档?我需要new: true因为我需要调用函数inbox.

Bla*_*ven 5

在的情况下.findOneAndUpdate(),或任何一种.findAndModify()核心的猫鼬驱动程序变种,实际回调签名具有“三防”参数:

 function(err,result,raw)
Run Code Online (Sandbox Code Playgroud)

第一个是任何错误响应,然后是根据选项的修改或原始文档,第三个是已发布语句的写入结果。

第三个参数应该像这样返回数据:

{ lastErrorObject:
   { updatedExisting: false,
     n: 1,
     upserted: 55e12c65f6044f57c8e09a46 },
  value: { _id: 55e12c65f6044f57c8e09a46, 
           number: 55555555, 
           country: 'US', 
           token: "XXX", 
           appInstalled: true,
           __v: 0 },
  ok: 1 }
Run Code Online (Sandbox Code Playgroud)

根据是否发生 upsert 的结果,lastErrorObject.updatedExisting其中的一致字段为两者true/false之一。请注意,_id当此属性为 时false,还有一个“更新”值包含对新文档的响应,但当它为 时则不包含true

因此,您将修改您的处理以考虑第三个条件,但这仅适用于回调而不是承诺:

Inbox.model.findOneAndUpdate(
    { "number": req.phone.number },
    { 
      "$set": {
          "country": req.phone.country,
          "token": hat(),
          "appInstalled": true
      }
    }, 
    { "new": true, "upsert": true },
    function(err,doc,raw) {

      if ( !raw.lastErrorObject.updatedExitsing ) {
         // do things with the new document created
      }
    }
);
Run Code Online (Sandbox Code Playgroud)

我还强烈建议您在这里使用更新运算符而不是原始对象,因为原始对象将始终覆盖整个文档,而运算符之类的$set只会影响列出的字段。

还要注意,只要它们的值是未找到的精确匹配,语句的任何匹配“查询参数”都会自动分配到新文档中。

鉴于出于某种原因,使用承诺似乎不会返回附加信息,那么除了设置之外,看不到承诺是如何实现的,{ new: false}并且基本上当没有返回文档时,它就是一个新文档。

无论如何,您都拥有预期要插入的所有文档数据,因此您实际上并不需要返回该数据。实际上,本机驱动程序方法是如何在核心处理此问题的,并且仅_id在发生 upsert 时才以“upsert ”值进行响应。

这实际上归结为本网站上讨论的另一个问题,位于:

承诺可以有多个 onFulfilled 参数吗?

这实际上归结为承诺响应中多个对象的解析,这在本机规范中不直接支持,但那里列出了一些方法。

因此,如果您实现 Bluebird 承诺并在.spread()那里使用该方法,那么一切都很好:

var async = require('async'),
    Promise = require('bluebird'),
    mongoose = require('mongoose'),
    Schema = mongoose.Schema;

mongoose.connect('mongodb://localhost/test');

var testSchema = new Schema({
  name: String
});

var Test = mongoose.model('Test',testSchema,'test');
Promise.promisifyAll(Test);
Promise.promisifyAll(Test.prototype);

async.series(
  [
    function(callback) {
      Test.remove({},callback);
    },
    function(callback) {
      var promise = Test.findOneAndUpdateAsync(
        { "name": "Bill" },
        { "$set": { "name": "Bill" } },
        { "new": true, "upsert": true }
      );

      promise.spread(function(doc,raw) {
        console.log(doc);
        console.log(raw);
        if ( !raw.lastErrorObject.updatedExisting ) {
          console.log( "new document" );
        }
        callback();
      });
    }
  ],
  function(err) {
    if (err) throw err;
    mongoose.disconnect();
  }
);
Run Code Online (Sandbox Code Playgroud)

这当然会返回两个对象,然后您可以一致地访问:

{ _id: 55e14b7af6044f57c8e09a4e, name: 'Bill', __v: 0 }
{ lastErrorObject:
   { updatedExisting: false,
     n: 1,
     upserted: 55e14b7af6044f57c8e09a4e },
  value: { _id: 55e14b7af6044f57c8e09a4e, name: 'Bill', __v: 0 },
  ok: 1 }
Run Code Online (Sandbox Code Playgroud)

这是一个完整的清单,展示了正常行为:

var async = require('async'),
    mongoose = require('mongoose'),
    Schema = mongoose.Schema;

mongoose.connect('mongodb://localhost/test');

var testSchema = new Schema({
  name: String
});

var Test = mongoose.model('Test',testSchema,'test');

async.series(
  [
    function(callback) {
      Test.remove({},callback);
    },
    function(callback) {
      Test.findOneAndUpdate(
        { "name": "Bill" },
        { "$set": { "name": "Bill" } },
        { "new": true, "upsert": true }
      ).then(function(doc,raw) {
        console.log(doc);
        console.log(raw);
        if ( !raw.lastErrorObject.updatedExisting ) {
          console.log( "new document" );
        }
        callback();
      });
    }
  ],
  function(err) {
    if (err) throw err;
    mongoose.disconnect();
  }
);
Run Code Online (Sandbox Code Playgroud)

作为记录,本机驱动程序本身没有这个问题,因为响应对象实际上只是除了任何错误之外返回的对象:

var async = require('async'),
    mongodb = require('mongodb'),
    MongoClient = mongodb.MongoClient;

MongoClient.connect('mongodb://localhost/test',function(err,db) {

  var collection = db.collection('test');

  collection.findOneAndUpdate(
    { "name": "Bill" },
    { "$set": { "name": "Bill" } },
    { "upsert": true, "returnOriginal": false }
  ).then(function(response) {
    console.log(response);
  });
});
Run Code Online (Sandbox Code Playgroud)

所以它总是这样的:

{ lastErrorObject:
   { updatedExisting: false,
     n: 1,
     upserted: 55e13bcbf6044f57c8e09a4b },
  value: { _id: 55e13bcbf6044f57c8e09a4b, name: 'Bill' },
  ok: 1 }
Run Code Online (Sandbox Code Playgroud)