我需要使用express和mongodb模块检索集合列表。 首先,我检索了一个有效的集合名称列表,然后我在循环中检索那些给定集合的数据。我的问题出在getColAsync():
getColAsync() {
return new Promise((resolve, reject) => {
this.connectDB().then((db) => {
var allCols = [];
let dbase = db.db(this.databaseName);
dbase.listCollections().toArray((err, collectionNames) => {
if(err) {
console.log(err);
reject(err);
}
else {
for(let i = 0; i < collectionNames.length; i++) {
dbase.collection(collectionNames[i].name.toString()).find({}).toArray((err, collectionData) => {
console.log("current collection data: " + collectionData);
allCols[i] = collectionData;
})
}
console.log("done getting all data");
resolve(allCols);
}
})
})
})
}
connectDB() {
if(this.dbConnection) {
// if connection exists
return this.dbConnection;
}
else {
this.dbConnection = new Promise((resolve, reject) => {
mongoClient.connect(this.URL, (err, db) => {
if(err) {
console.log("DB Access: Error on mongoClient.connect.");
console.log(err);
reject(err);
}
else {
console.log("DB Access: resolved.");
resolve(db);
}
});
});
console.log("DB Access: db exists. Connected.");
return this.dbConnection;
}
}
在我检索每个集合的forloop中,调用console.log(“完成获取所有数据”)并在forloop开始之前解析promise。例如:
done getting all data
current collection data: something
current collection data: something2
current collection data: something3
请帮忙
答案 0 :(得分:1)
代码中的问题是这一部分:
for (let i = 0; i < collectionNames.length; i++) {
dbase.collection(collectionNames[i].name.toString()).find({}).toArray((err, collectionData) => {
console.log("current collection data: " + collectionData);
allCols[i] = collectionData;
})
}
console.log("done getting all data");
resolve(allCols);
您应该注意到resolve(allCols);
循环结束后立即调用for
,但循环的每次迭代都不会等待toArray
回调称为
行dbase.collection(collectionNames[i].name.toString()).find({}).toArray(callback)
是异步的,因此循环将结束,您将调用resolve(allCols);
,但.find({}).toArray
代码尚未完成。
所以,基本上你做的是:
allCols = []
你应该做的是:
关键是Promise.all([/* array of promises */])
函数接受一个promises数组并返回一个Promise
本身,向下游传递一个包含所有结果的数组,所以我们需要获得的是这样的:
const dataPromises = []
for (let i = 0; i < collectionNames.length; i++) {
dataPromises[i] = /* data fetch promise */;
}
return Promise.all(dataPromises);
正如您所看到的,最后一行是return Promise.all(dataPromises);
而不是代码中的resolve(allCols)
,因此我们无法再在new Promise(func)
构造函数中执行此代码。
相反,我们应该将Promise
链接到.then()
,如下所示:
getColAsync() {
return this.connectDB().then((db) => {
let dbase = db.db(this.databaseName);
const dataPromises = []
dbase.listCollections().toArray((err, collectionNames) => {
if (err) {
console.log(err);
return Promise.reject(err);
} else {
for (let i = 0; i < collectionNames.length; i++) {
dataPromises[i] = new Promise((res, rej) => {
dbase.collection(collectionNames[i].name.toString()).find({}).toArray((err, collectionData) => {
console.log("current collection data: " + collectionData);
if (err) {
console.log(err);
reject(err);
} else {
resolve(collectionData);
}
});
});
}
console.log("done getting all data");
return Promise.all(dataPromises);
}
});
})
}
现在请注意我们返回一个return this.connectDB().then(...)
,然后返回Promise.all(dataPromises);
这个返回的新Promise
在每个步骤让我们保持Promise
链,从而{ {1}}本身会返回getColAsync()
,然后您可以使用Promise
和.then()
进行处理。
你可以稍微清理你的代码:
.catch()
如您所见,主要变化是:
getColAsync() {
return this.connectDB().then((db) => {
let dbase = db.db(this.databaseName);
const dataPromises = []
// dbase.listCollections().toArray() returns a promise itself
return dbase.listCollections().toArray()
.then((collectionsInfo) => {
// collectionsInfo.map converts an array of collection info into an array of selected
// collections
return collectionsInfo.map((info) => {
return dbase.collection(info.name);
});
})
}).then((collections) => {
// collections.map converts an array of selected collections into an array of Promises
// to get each collection data.
return Promise.all(collections.map((collection) => {
return collection.find({}).toArray();
}))
})
}
轻松将数据数组转换为新数组下面我还介绍了使用回调函数和我正在处理的模块的代码变体。
我最近致力于this npm module,以帮助获得更清晰,更易读的Promises组合。
在您的情况下,我使用Array.map
函数来处理您选择数据库并获取集合信息列表的第一步:
fCombine
这会产生一个承诺,向下游传递一个对象Promise.fCombine({
dbase: (dbURL, done) => mongoClient.connect(dbURL, done),
collInfos: ({ dbase }, done) => getCollectionsInfo(dbase, done),
}, { dbURL: this.URL })
。其中{dbase: /* the db instance */, collInfos: [/* the list of collections info */]}
是具有回调模式的函数,如下所示:
getCollectionNames(dbase, done)
现在,您可以链接先前的Promise并将集合信息列表转换为选定的db集合,如下所示:
getCollectionsInfo = (db, done) => {
let dbase = db.db(this.databaseName);
dbase.listCollections().toArray(done);
}
现在下游我们有一个来自db的选定集合的列表,我们应该从每个集合中获取数据,然后将数据中的结果与集合数据合并。
在我的模块中,我有一个Promise.fCombine({
dbase: ({ dbURL }, done) => mongoClient.connect(dbURL, done),
collInfos: ({ dbase }, done) => getCollectionsInfo(dbase, done),
}, { dbURL: this.URL }).then(({ dbase, collInfos }) => {
return Promise.resolve(collInfos.map((info) => {
return dbase.collection(info.name);
}));
})
选项,可以创建一个_mux
来模仿常规PromiseMux
的行为和组合模式,但它实际上在同一个Promise上工作时间。每个Promise都从下游集合数组中输入一个项目,因此您可以编写代码以从泛型集合中获取数据,并且将对数组中的每个集合执行:
Promise
在上面的代码中,Promise.fCombine({
dbase: ({ dbURL }, done) => mongoClient.connect(dbURL, done),
collInfos: ({ dbase }, done) => getCollectionsInfo(dbase, done),
}, { dbURL: this.URL }).then(({ dbase, collInfos }) => {
return Promise.resolve(collInfos.map((info) => {
return dbase.collection(info.name);
}));
})._mux((mux) => {
return mux._fReduce([
(collection, done) => collection.find({}).toArray(done)
]).deMux((allCollectionsData) => {
return Promise.resolve(allCollectionsData);
})
});
的行为类似于_fReduce
,但是它接受带回调而不是对象的函数数组,它只向下游传递最后一个函数的结果(不是结构化对象)所有的结果)。最后,_fCombine
会在多路复用器的每个deMux
同时执行Promise.all
,并将其结果汇总在一起。
因此整个代码看起来像这样:
Promise
在我的模块中,我试图避免最常见的反模式思想,但我仍然会有一些Ghost承诺。
使用来自mongodb的承诺,这将变得更加清洁:
getCollectionsInfo = (db, done) => {
let dbase = db.db(this.databaseName);
dbase.listCollections().toArray(done);
}
getCollAsync = () => {
return Promise.fCombine({
/**
* fCombine uses an object whose fields are functions with callback pattern to
* build consecutive Promises. Each subsequent functions gets as input the results
* from previous functions.
* The second parameter of the fCombine is the initial value, which in our case is
* the db url.
*/
dbase: ({ dbURL }, done) => mongoClient.connect(dbURL, done), // connect to DB, return the connected dbase
collInfos: ({ dbase }, done) => getCollectionsInfo(dbase, done), // fetch collection info from dbase, return the info objects
}, { dbURL: this.URL }).then(({ dbase, collInfos }) => {
return Promise.resolve(collInfos.map((info) => {
/**
* we use Array.map to convert collection info into
* a list of selected db collections
*/
return dbase.collection(info.name);
}));
})._mux((mux) => {
/**
* _mux splits the list of collections returned before into a series of "simultaneous promises"
* which you can manipulate as if they were a single Promise.
*/
return mux._fReduce([ // this fReduce here gets as input a single collection from the retrieved list
(collection, done) => collection.find({}).toArray(done)
]).deMux((allCollectionsData) => {
// finally we can put back together all the results.
return Promise.resolve(allCollectionsData);
})
});
}