jjzjj

javascript - 函数 insertMany() 无序 : proper way to get both the errors and the result?

coder 2023-10-31 原文

好像是 MongoDB insertMany()将ordered 选项设置为false 的函数可以比ordered 选项设置为true 更有效地插入文档。即使多个文档插入失败,它也可以继续插入文档。

但是我发现没有一种干净的方法可以同时获取每个失败文档的错误和整体命令结果。

(顺便说一句,我使用的是 Node.js 驱动程序 API 2.2。从现在开始我将引用驱动程序的源代码: http://mongodb.github.io/node-mongodb-native/2.2/api/lib_collection.js.html )

首先,如果使用 Promise,则无法同时获取错误和结果。在源代码第 540 行,insertMany()返回错误或结果 - 不是两者,而 bulkWrite()回调在源代码第 703 行返回两者。

其次,如果使用回调,事情会变得更糟。当bulkWrite()调用带有错误和结果的回调函数,insertMany()使用错误和结果调用回调,但结果是 BulkWrite 的结果,而不是正确转换为 InsertManyResults 的结果。请参阅源代码第 535 行。我认为这是一个错误。

甚至对于 bulkWrite() , 当错误数为 1 时,它不会将结果正确转换为其格式。请参阅源代码第669行。我认为这也是一个错误。

现在我认为 Node.js 驱动程序根本没有准备好处理这种情况。

目前,似乎没有办法同时正确获取错误和结果。

我对吗?

更新

我运行了一个基于 Neil Lunn 的回答的测试代码。由于我的
Node.js(4.4.5) 不理解 async/await,我不得不用显式的 Promise 重写测试代码。

测试代码如下:

function doTest()
{
    var MongoClient = require('mongodb').MongoClient;
    var testData = [ 1,2,2,3,3,4,5,6,7,8,9 ];
    var db;

    return MongoClient.connect('mongodb://127.0.0.1/test')
    .then(function (_db)
    {
        db = _db;
        return db.createCollection('test');
    })
    .then(function ()
    {
        return db.collection('test').deleteMany({})
        .then(function ()
        {
            return db.collection('test').insertMany(
                testData.map(function (_id)
                {
                    return { _id: _id };
                }),
                { ordered: false })
            .then(function (result)
            {
                console.log('Promise: result', result);
            }, function (err)
            {
                console.log('Promise: error', err);
            });
        })
        .then(function ()
        {
            return db.collection('test').deleteMany({});
        })
        .then(function ()
        {
            return new Promise(function (resolve, reject)
            {
                return db.collection('test').insertMany(
                    testData.map(function (_id)
                    {
                        return { _id: _id };
                    }),
                    { ordered: false },
                    function (err, result)
                {
                    console.log('callback: error', err);
                    console.log('callback: result', result);
                    console.log('callback: result.hasWriteErrors', result.hasWriteErrors());
                    console.log('callback: result.getWriteErrors',
                        JSON.stringify(result.getWriteErrors(), null, 2));
                    resolve();
                });
            });
        });
    })
    .catch(function (err)
    {
        console.log('catch', err);
    })
    .then(function ()
    {
        db.close();
    });
}
doTest();

结果如下:
Promise: error { [MongoError: write operation failed]
  name: 'MongoError',
  message: 'write operation failed',
  driver: true,
  code: 11000,
  writeErrors: 
   [ { code: [Getter],
       index: [Getter],
       errmsg: [Getter],
       getOperation: [Function],
       toJSON: [Function],
       toString: [Function] },
     { code: [Getter],
       index: [Getter],
       errmsg: [Getter],
       getOperation: [Function],
       toJSON: [Function],
       toString: [Function] } ] }
callback: error { [MongoError: write operation failed]
  name: 'MongoError',
  message: 'write operation failed',
  driver: true,
  code: 11000,
  writeErrors: 
   [ { code: [Getter],
       index: [Getter],
       errmsg: [Getter],
       getOperation: [Function],
       toJSON: [Function],
       toString: [Function] },
     { code: [Getter],
       index: [Getter],
       errmsg: [Getter],
       getOperation: [Function],
       toJSON: [Function],
       toString: [Function] } ] }
callback: result { ok: [Getter],
  nInserted: [Getter],
  nUpserted: [Getter],
  nMatched: [Getter],
  nModified: [Getter],
  nRemoved: [Getter],
  getInsertedIds: [Function],
  getUpsertedIds: [Function],
  getUpsertedIdAt: [Function],
  getRawResponse: [Function],
  hasWriteErrors: [Function],
  getWriteErrorCount: [Function],
  getWriteErrorAt: [Function],
  getWriteErrors: [Function],
  getLastOp: [Function],
  getWriteConcernError: [Function],
  toJSON: [Function],
  toString: [Function],
  isOk: [Function],
  insertedCount: 9,
  matchedCount: 0,
  modifiedCount: 0,
  deletedCount: 0,
  upsertedCount: 0,
  upsertedIds: {},
  insertedIds: 
   { '0': 1,
     '1': 2,
     '2': 2,
     '3': 3,
     '4': 3,
     '5': 4,
     '6': 5,
     '7': 6,
     '8': 7,
     '9': 8,
     '10': 9 },
  n: 9 }
callback: result.hasWriteErrors true
callback: result.getWriteErrors [
  {
    "code": 11000,
    "index": 2,
    "errmsg": "E11000 duplicate key error collection: test.test index: _id_ dup key: { : 2 }",
    "op": {
      "_id": 2
    }
  },
  {
    "code": 11000,
    "index": 4,
    "errmsg": "E11000 duplicate key error collection: test.test index: _id_ dup key: { : 3 }",
    "op": {
      "_id": 3
    }
  }
]

现在,我再次运行代码,将 testData 变量修改如下:
var testData = [ 1,2,3,3,4,5,6,7,8,9 ];

在这种情况下,错误数将为 1,而不是 2,因为重复的“2”已被删除。

结果如下:
Promise: error { [MongoError: E11000 duplicate key error collection: test.test index: _id_ dup key: { : 3 }]
  name: 'MongoError',
  message: 'E11000 duplicate key error collection: test.test index: _id_ dup key: { : 3 }',
  driver: true,
  code: 11000,
  index: 3,
  errmsg: 'E11000 duplicate key error collection: test.test index: _id_ dup key: { : 3 }',
  getOperation: [Function],
  toJSON: [Function],
  toString: [Function] }
callback: error { [MongoError: E11000 duplicate key error collection: test.test index: _id_ dup key: { : 3 }]
  name: 'MongoError',
  message: 'E11000 duplicate key error collection: test.test index: _id_ dup key: { : 3 }',
  driver: true,
  code: 11000,
  index: 3,
  errmsg: 'E11000 duplicate key error collection: test.test index: _id_ dup key: { : 3 }',
  getOperation: [Function],
  toJSON: [Function],
  toString: [Function] }
callback: result { ok: [Getter],
  nInserted: [Getter],
  nUpserted: [Getter],
  nMatched: [Getter],
  nModified: [Getter],
  nRemoved: [Getter],
  getInsertedIds: [Function],
  getUpsertedIds: [Function],
  getUpsertedIdAt: [Function],
  getRawResponse: [Function],
  hasWriteErrors: [Function],
  getWriteErrorCount: [Function],
  getWriteErrorAt: [Function],
  getWriteErrors: [Function],
  getLastOp: [Function],
  getWriteConcernError: [Function],
  toJSON: [Function],
  toString: [Function],
  isOk: [Function] }
callback: result.hasWriteErrors true
callback: result.getWriteErrors [
  {
    "code": 11000,
    "index": 3,
    "errmsg": "E11000 duplicate key error collection: test.test index: _id_ dup key: { : 3 }",
    "op": {
      "_id": 3
    }
  }
]

错误和结果的格式与第一次运行完全不同。
  • 该错误没有 writeErrors 字段。
  • 结果没有“转换”字段。 (insertedCount、matchedCount 等)正如我上面所说,这是驱动程序源代码第 669 行上的一个“错误”。

  • 在两次测试运行中,结果参数的类型都不是 Collection~insertWriteOpResult .第一个是 Collection~bulkWriteOpCallback ,第二个是更内部的。因此,在这种情况下,API 文档是错误的。正如我上面所说,这是由第 535 和 669 行的“错误”引起的。

    因此,即使结果可以使用(实际上,正如 Neil Lunn 所说,结果有 hasWriteErrors()getWriteErrors()),由于没有记录此行为,我怀疑它可以在没有通知的情况下在以后的版本中更改,并且我的代码会中断。

    最佳答案

    问题实际上仅在于“Promise”如何解决以及错误信息如何传递,但当然真正的核心问题是存在 实际上,当任何“批量”操作设置为 { ordered: false } 时,都会返回“两者”错误和结果信息。 .如 NODE-1158 所述,此问题已在 3.x 版本的驱动程序中得到解决。其中还包含指向修复该问题的 future 分支中的提交的链接。

    对此的“解决方法”是要注意,BulkWriteResult 中存在“两者”结果和错误信息。作为任何此类方法的“回调”调用结果返回的对象(注意 insertMany() 甚至 bulkWrite() 实际上包装了一个底层 Bulk API Implementation )。

    用列表来演示:

    const MongoClient = require('mongodb').MongoClient;
    
    const uri = 'mongodb://localhost/test';
    const testData = [1,2,3,3,4,5,6,6,7,8,9];
    
    (async function() {
    
      let db;
    
      try {
    
        db = await MongoClient.connect(uri);
    
        await db.collection('test').remove();
    
        // Expect an error here - but it's just the errors
        try {
          let result = await db.collection('test').insertMany(
            testData.map( _id => ({ _id }) ),
            { "ordered": false }
          );
          console.log(result);   // never gets here
        } catch(e) {
          console.dir(e);
          console.log(JSON.stringify(e.writeErrors,undefined,2));
        }
    
        await db.collection('test').remove();
        // Wrapped callback so we see what happens
    
        try {
          let result = await new Promise((resolve,reject) => 
            db.collection('test').insertMany(
              testData.map( _id => ({ _id }) ),
              { "ordered": false },
              (err,result) => {
                if (err) reject(result);    // Because the errors are here as well
                resolve(result);
              }
            )
          );
          console.log(result);  // Never gets here
        } catch(e) {
          console.dir(e);
          console.log(e.hasWriteErrors());
          console.log(JSON.stringify(e.getWriteErrors(),undefined,2));
        }
    
      } catch(e) {
        console.error(e);
      } finally {
        db.close();
      }
    
    })();
    

    所以有两个代码块试图使用 insertMany()带有将要为某些值产生重复键错误的值列表。

    在第一次尝试中,我们使用默认值 Promise应该由驱动程序的实现代码指示的返回只是将传递 err回调结果在它包装的方法中是 reject()陈述。这意味着我们去catch在这里阻塞并产生错误信息作为输出:
    { MongoError: [object Object]
        at Function.MongoError.create (/home/neillunn/projects/bulkerror/node_modules/mongodb-core/lib/error.js:31:11)
        at toError (/home/neillunn/projects/bulkerror/node_modules/mongodb/lib/utils.js:139:22)
        at /home/neillunn/projects/bulkerror/node_modules/mongodb/lib/collection.js:701:23
        at handleCallback (/home/neillunn/projects/bulkerror/node_modules/mongodb/lib/utils.js:120:56)
        at /home/neillunn/projects/bulkerror/node_modules/mongodb/lib/bulk/unordered.js:465:9
        at handleCallback (/home/neillunn/projects/bulkerror/node_modules/mongodb/lib/utils.js:120:56)
        at resultHandler (/home/neillunn/projects/bulkerror/node_modules/mongodb/lib/bulk/unordered.js:413:5)
        at /home/neillunn/projects/bulkerror/node_modules/mongodb-core/lib/connection/pool.js:469:18
        at _combinedTickCallback (internal/process/next_tick.js:131:7)
        at process._tickCallback (internal/process/next_tick.js:180:9)
      name: 'MongoError',
      message: 'write operation failed',
      driver: true,
      code: 11000,
      writeErrors:
       [ WriteError {
           code: [Getter],
           index: [Getter],
           errmsg: [Getter],
           getOperation: [Function],
           toJSON: [Function],
           toString: [Function] },
         WriteError {
           code: [Getter],
           index: [Getter],
           errmsg: [Getter],
           getOperation: [Function],
           toJSON: [Function],
           toString: [Function] } ] }
    [
      {
        "code": 11000,
        "index": 3,
        "errmsg": "E11000 duplicate key error collection: test.test index: _id_ dup key: { : 3 }",
        "op": {
          "_id": 3
        }
      },
      {
        "code": 11000,
        "index": 7,
        "errmsg": "E11000 duplicate key error collection: test.test index: _id_ dup key: { : 6 }",
        "op": {
          "_id": 6
        }
      }
    ]
    

    请注意,这是一个包装好的 MongoError尽管我们设置了 { ordered: false }响应中没有“结果”信息。详细查看WriteError列表中的错误信息我们可以看到确实有关于产生的每个重复键错误的详细信息。

    因此,批处理中的所有内容都成功写入并没有引发错误,但在任何可获得的内容中都没有报告 来自 Promise .但是底层方法不是这样,它仍然使用回调来实现。

    第二次尝试“手动”包装此回调,因此我们实际上可以通过更改行为并传递 result 来查看结果。反对 rejecterr存在。这告诉我们一个不同的故事:
    BulkWriteResult {
      ok: [Getter],
      nInserted: [Getter],
      nUpserted: [Getter],
      nMatched: [Getter],
      nModified: [Getter],
      nRemoved: [Getter],
      getInsertedIds: [Function],
      getUpsertedIds: [Function],
      getUpsertedIdAt: [Function],
      getRawResponse: [Function],
      hasWriteErrors: [Function],
      getWriteErrorCount: [Function],
      getWriteErrorAt: [Function],
      getWriteErrors: [Function],
      getLastOp: [Function],
      getWriteConcernError: [Function],
      toJSON: [Function],
      toString: [Function],
      isOk: [Function],
      insertedCount: 9,
      matchedCount: 0,
      modifiedCount: 0,
      deletedCount: 0,
      upsertedCount: 0,
      upsertedIds: {},
      insertedIds:
       { '0': 1,
         '1': 2,
         '2': 3,
         '3': 3,
         '4': 4,
         '5': 5,
         '6': 6,
         '7': 6,
         '8': 7,
         '9': 8,
         '10': 9 },
      n: 9 }
    true
    [
      {
        "code": 11000,
        "index": 3,
        "errmsg": "E11000 duplicate key error collection: test.test index: _id_ dup key: { : 3 }",
        "op": {
          "_id": 3
        }
      },
      {
        "code": 11000,
        "index": 7,
        "errmsg": "E11000 duplicate key error collection: test.test index: _id_ dup key: { : 6 }",
        "op": {
          "_id": 6
        }
      }
    ]
    

    由于我们没有传回err我们现在看到 BulkWriteResult在 catch 块中。我们知道我们到达那里是因为我们在该块中运行的特定代码来检查结果。

    常规结果确实有一些东西,如修改或插入的计数,以及insertedIds的列表。 .从检查中我们还可以看出hasWriteErrors()返回 true ,我们可以得到WriteError的列表我们已将其连载以便更好地观看。

    在 3.x 中修复

    如链接问题所述,实际修复将仅出现在支持 MongoDB 3.6 的 3.x 驱动程序版本中。 “修复”基本上是在对 的“较低级别”完成的。不是 返回 BulkWriteResult完全没有,而是让 err返回 BulkWriteError .

    这实际上使事情与其他一些驱动程序已经正确实现这一点的方式更加一致。老实说,这是传统“Node 风格”回调的“宿醉”,其中总是返回“两者”错误和响应。

    因此,将其转化为“只是一个错误”会使事情更加一致,并且按您通常的预期工作。

    作为旁注,MongoDB Node.js native driver silently swallows bulkWrite exception. 中的相关问题JIRA 问题中引用的显示实际 bulkWrite()实现中的方法(不是“直接” insertMany() 包装的内容)有一个稍微不同的问题,即实际上根本抛出“没有错误”,因为代码期待 result成为 null正如所描述的那样,它不是。

    所以相反的情况是真实的,我们永远不会到达 catch对于使用 Promise 的默认实现中的异常.然而,应该应用完全相同的处理方法,通过手动包装回调并发送 result通过 reject优先于 err当这两个都返回为 not null 时.

    按照描述解决这些问题,最好在新驱动程序可用时立即迁移到新驱动程序。无论如何,这应该很快。

    关于javascript - 函数 insertMany() 无序 : proper way to get both the errors and the result?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46841162/

    有关javascript - 函数 insertMany() 无序 : proper way to get both the errors and the result?的更多相关文章

    1. ruby - 在没有 sass 引擎的情况下使用 sass 颜色函数 - 2

      我想在一个没有Sass引擎的类中使用Sass颜色函数。我已经在项目中使用了sassgem,所以我认为搭载会像以下一样简单:classRectangleincludeSass::Script::FunctionsdefcolorSass::Script::Color.new([0x82,0x39,0x06])enddefrender#hamlengineexecutedwithcontextofself#sothatwithintemlateicouldcall#%stop{offset:'0%',stop:{color:lighten(color)}}endend更新:参见上面的#re

    2. ruby-on-rails - 在 ruby​​ 中使用 gsub 函数替换单词 - 2

      我正在尝试用ruby​​中的gsub函数替换字符串中的某些单词,但有时效果很好,在某些情况下会出现此错误?这种格式有什么问题吗NoMethodError(undefinedmethod`gsub!'fornil:NilClass):模型.rbclassTest"replacethisID1",WAY=>"replacethisID2andID3",DELTA=>"replacethisID4"}end另一个模型.rbclassCheck 最佳答案 啊,我找到了!gsub!是一个非常奇怪的方法。首先,它替换了字符串,所以它实际上修改了

    3. ruby - 在 Ruby 中有条件地定义函数 - 2

      我有一些代码在几个不同的位置之一运行:作为具有调试输出的命令行工具,作为不接受任何输出的更大程序的一部分,以及在Rails环境中。有时我需要根据代码的位置对代码进行细微的更改,我意识到以下样式似乎可行:print"Testingnestedfunctionsdefined\n"CLI=trueifCLIdeftest_printprint"CommandLineVersion\n"endelsedeftest_printprint"ReleaseVersion\n"endendtest_print()这导致:TestingnestedfunctionsdefinedCommandLin

    4. ruby - 在 Ruby 中按名称传递函数 - 2

      如何在Ruby中按名称传递函数?(我使用Ruby才几个小时,所以我还在想办法。)nums=[1,2,3,4]#Thisworks,butismoreverbosethanI'dlikenums.eachdo|i|putsiend#InJS,Icouldjustdosomethinglike:#nums.forEach(console.log)#InF#,itwouldbesomethinglike:#List.iternums(printf"%A")#InRuby,IwishIcoulddosomethinglike:nums.eachputs在Ruby中能不能做到类似的简洁?我可以只

    5. C51单片机——实现用独立按键控制LED亮灭(调用函数篇) - 2

      说在前面这部分我本来是合为一篇来写的,因为目的是一样的,都是通过独立按键来控制LED闪灭本质上是起到开关的作用,即调用函数和中断函数。但是写一篇太累了,我还是决定分为两篇写,这篇是调用函数篇。在本篇中你主要看到这些东西!!!1.调用函数的方法(主要讲语法和格式)2.独立按键如何控制LED亮灭3.程序中的一些细节(软件消抖等)1.调用函数的方法思路还是比较清晰地,就是通过按下按键来控制LED闪灭,即每按下一次,LED取反一次。重要的是,把按键与LED联系在一起。我打算用K1来作为开关,看了一下开发板原理图,K1连接的是单片机的P31口,当按下K1时,P31是与GND相连的,也就是说,当我按下去时

    6. ruby-on-rails - 将字符串转换为 ruby​​-on-rails 中的函数 - 2

      我需要一个通过输入字符串进行计算的方法,像这样function="(a/b)*100"a=25b=50function.something>>50有什么方法吗? 最佳答案 您可以使用instance_eval:function="(a/b)*100"a=25.0b=50instance_evalfunction#=>50.0请注意,使用eval本质上是不安全的,尤其是当您使用外部输入时,因为它可能包含注入(inject)的恶意代码。另请注意,a设置为25.0而不是25,因为如果它是整数a/b将导致0(整数)。

    7. ruby-on-rails - 使用 javascript 更改数据方法不会更改 ajax 调用用户的什么方法? - 2

      我遇到了一个非常奇怪的问题,我很难解决。在我看来,我有一个与data-remote="true"和data-method="delete"的链接。当我单击该链接时,我可以看到对我的Rails服务器的DELETE请求。返回的JS代码会更改此链接的属性,其中包括href和data-method。再次单击此链接后,我的服务器收到了对新href的请求,但使用的是旧的data-method,即使我已将其从DELETE到POST(它仍然发送一个DELETE请求)。但是,如果我刷新页面,HTML与"new"HTML相同(随返回的JS发生变化),但它实际上发送了正确的请求类型。这就是这个问题令我困惑的

    8. ruby - 在 ruby​​ 中使用 .try 函数和 .map 函数 - 2

      我需要从json记录中获取一些值并像下面这样提取curr_json_doc['title']['genre'].map{|s|s['name']}.join(',')但对于某些记录,curr_json_doc['title']['genre']可以为空。所以我想对map和join()使用try函数。我试过如下curr_json_doc['title']['genre'].try(:map,{|s|s['name']}).try(:join,(','))但是没用。 最佳答案 你没有正确传递block。block被传递给参数括号外的方法

    9. ruby - 是否可以从也在该模块中的类内部调用模块函数 - 2

      在这段Ruby代码中:ModuleMClassC当我尝试运行时出现“'M:Module'的未定义方法'helper'”错误c=M::C.new("world")c.work但直接从另一个类调用M::helper("world")工作正常。类不能调用在定义它们的同一模块中定义的模块函数吗?除了将类移出模块外,还有其他解决方法吗? 最佳答案 为了调用M::helper,你需要将它定义为defself.helper;结束为了进行比较,请查看以下修改后的代码段中的helper和helper2moduleMclassC

    10. ruby - 将运算符传递给函数? - 2

      也许这听起来很荒谬,但我想知道这对Ruby是否可行?基本上我有一个功能...defadda,bc=a+breturncend我希望能够将“+”或其他运算符(例如“-”)传递给函数,这样它就类似于...defsuma,b,operatorc=aoperatorbreturncend这可能吗? 最佳答案 两种可能性:以方法/算子名作为符号:defsuma,b,operatora.send(operator,b)endsum42,23,:+或者更通用的解决方案:采取一个block:defsuma,byielda,bendsum42,23,

    随机推荐