Javascript 函数insertMany()无序:获取错误和结果的正确方法?
似乎将ordered选项设置为false的MongoDBJavascript 函数insertMany()无序:获取错误和结果的正确方法?,javascript,node.js,mongodb,Javascript,Node.js,Mongodb,似乎将ordered选项设置为false的MongoDBinsertMany()函数可以比将ordered选项设置为true更有效地插入文档。即使有几个文档无法插入,它也可以继续插入文档 但我发现没有一种干净的方法可以同时获得每个失败文档的错误和整个命令结果 (顺便说一句,我正在使用Node.js驱动程序API 2.2。从现在起,我将参考驱动程序的源代码:) 首先,如果使用Promise,则无法同时获得错误和结果。在源代码第540行,insertMany()返回错误或结果,而bulkWrite(
insertMany()
函数可以比将ordered选项设置为true更有效地插入文档。即使有几个文档无法插入,它也可以继续插入文档
但我发现没有一种干净的方法可以同时获得每个失败文档的错误和整个命令结果
(顺便说一句,我正在使用Node.js驱动程序API 2.2。从现在起,我将参考驱动程序的源代码:)
首先,如果使用Promise,则无法同时获得错误和结果。在源代码第540行,insertMany()
返回错误或结果,而bulkWrite()
回调在源代码第703行返回这两个结果
其次,如果使用回调,情况会变得更糟。当bulkWrite()
调用包含错误和结果的回调时,insertMany()
调用包含错误和结果的回调,但结果是bulkWrite的结果,而不是正确转换为InsertManyResults的结果。请参阅源代码第535行。我认为这是一个错误
即使对于bulkWrite()
,当错误数为1时,也无法将结果正确转换为其格式。请参阅源代码第669行。我认为这也是一个错误
现在我认为Node.js驱动程序根本不准备处理这个问题
就目前而言,似乎没有办法正确地获得错误和结果
我说得对吗
更新
我运行了一个基于Neil Lunn答案的测试代码。自从我
js(4.4.5)不理解async/await,我不得不用明确的承诺重写测试代码
测试代码如下所示:
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();
var testData = [ 1,2,3,3,4,5,6,7,8,9 ];
结果如下:
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
}
}
]
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
}
}
]
现在,我再次运行了代码,testData变量修改如下:
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();
var testData = [ 1,2,3,3,4,5,6,7,8,9 ];
在这种情况下,由于重复的“2”已被删除,因此错误数将为1,而不是2
结果如下:
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
}
}
]
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上的一个“bug”,正如我上面所说的
Collection~insertWriteOpResult
。第一个是Collection~bulkWriteOpCallback
,第二个是内部的。因此,在这种情况下,API文档是错误的。正如我上面所说的,这是由第535行和第669行的“bug”引起的
因此,即使结果可以使用(事实上,正如Neil Lunn所说,结果有
hasWriteErrors()
和getWriteErrors()
),因为这种行为没有文档记录,我怀疑它可以在不通知的情况下在以后的版本中更改,我的代码将被破坏。问题实际上只是如何“承诺”解析和如何传递错误信息,但真正的核心问题当然是,当任何“批量”操作设置为{ordered:false}
时,实际上返回了错误和结果信息。这在3.x版本的驱动程序中得到了解决,如上面所述,其中还包含指向“在将来提交”分支的链接,用于修复该问题
这方面的“解决方法”是要知道,作为任何此类方法的“回调”调用的结果返回的BulkWriteResult
对象中存在“两个”结果和错误信息(注意insertMany()
甚至bulkWrite()
实际上包装了底层)
要用列表演示:
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}
,但响应中没有“result”信息。在详细检查错误信息时,我们可以看到,WriteError
列表中确实有关于产生的每个重复密钥错误的详细信息
因此,批处理中所有未抛出错误的内容都成功写入,但从承诺
获得的任何内容中都未报告该错误。但底层方法并非如此,它仍然使用回调实现
第二次尝试“手动”包装此回调,因此我们实际上可以通过改变行为并在出现错误时将结果
对象传递给拒绝
来查看结果。这告诉我们一个不同的故事:
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
,我们现在在catch块中看到BulkWriteResult
。我们知道我们到达那里是因为我们在那个块中运行特定的代码来检查结果
常规结果确实包含一些内容,如修改或插入的计数,以及insertedIds
列表。我们还可以从检查中看到hasWriteErrors()
返回true
,并且我们可以获得WriteError
的列表,我们已经对其进行了序列化,以便更好地查看
固定在3.x中
正如链接问题上所指出的,实际的修复程序将只出现在支持MongoDB 3.6的3.x驱动程序版本中。“修复”基本上是在“较低级别”上完成的,以而不是返回BulkWriteR