我正在学习节点,我有一段代码可以查询要传递给回调函数的db中的数据。但是我只想返回唯一成员。
这意味着我必须遍历查询结果,并将唯一成员推入数组,然后将该数组传递给cb函数。
问题是,输入也是我构建查询的数组。所以已经有一个循环。
我已经看过一些以前的带有promise,await等的SO线程。但是在像这里这样的循环中实现它们时遇到了一些麻烦。
static getDestination(envName, cb){
var ans = new Array();
var promises;
DataBase.initDB((db) => {
for (var i = 0; i < envName.length; i++){
db.collection('Environment').find({'environmentName' : envName[i]}, (err, data) => {
if (err) {
console.log('error getting data in getDestination()');
return;
}
data.toArray((err, content) => {
if (err) {
console.log('error converting cursor into array');
return;
}
else {
for (const doc of content){
if (!ans.includes(doc.destination)){
ans.push(doc.destination);
}
//cb(ans);
}
}
});
});
}
})
cb(ans);
}
现在,回调只是console.log(ans),它正在打印出一个空数组[]。
如果我在最内层的循环中调用回调,它将返回适当的成员,但在循环进行时会被调用,如下所示:
[]
[ 'DEV' ]
[ 'DEV' ]
[ 'DEV', 'QA' ]
我只想获取输出的最后一行。详尽的解释也将是惊人的!
编辑:据我了解,我应该创建一个Promise数组,并且在循环的每次迭代中,我都应该使用该操作作为参数来创建一个新的Promise。然后在一切结束时,我可以打电话
Promise.all(promises).then(cb(ans));
但是我在理解应该创建承诺的交易上遇到了麻烦。因为如果我在最内层的循环中创建诺言,则甚至在第一个循环创建之前就调用了循环外的cb。
编辑:
exports.initDB = (cb) => {
console.log('Connecting...');
const uri = <mongo connection uri here>;
const client = new MongoClient(uri, { useNewUrlParser: true });
client.connect(err => {
if (err) {
console.log('eror connecting to db');
console.log(err);
return;
}
cb(client.db(process.env.DB_NAME));
});
}
答案 0 :(得分:1)
在这里,您将创建承诺数组并使用它来汇总ans
,然后调用一次cb()
。我还建议使用cb(error, result)
的{{3}}而不是cb(result)
,因为有多个步骤可能会出错,并且应该在查询成功或失败时通知getDestination()
的调用方当它失败时。
static getDestination (envName, cb) {
DataBase.initDB(db => {
const envCollection = db.collection('Environment');
const promises = envName.map(value => new Promise((resolve, reject) => {
envCollection.find({ environmentName: value }, (error, data) => {
if (error) return reject(error);
data.toArray((error, content) => {
if (error) reject(error);
else resolve(content);
});
});
}));
Promise.all(promises).then(contents => {
const ans = contents.reduce(
(ans, content) => content.reduce(
(ans, doc) => ans.add(doc.destination),
ans
),
new Set()
);
cb(null, [...ans]);
}).catch(error => {
cb(error);
});
});
}
请注意,每个the Node.js callback convention是同步构造的,并且resolve()
或reject()
被异步调用。这是必需的,因为您的数据库查询尚未返回Promise
。
如果您使用的API确实会返回承诺,则应避免使用new Promise()
并从现有的承诺中链接。
我还通过使用explicit promise construction antipattern而不是数组来优化了ans
的聚合,因此实际上我们可以省略对现有值的检查,因为集合不像数组那样只保留每个唯一值中的一个。
如果您希望getDestination()
函数返回一个Promise而不是接受回调,则可以这样重写它:
static getDestination (envName) {
return new Promise(resolve => {
DataBase.initDB(resolve);
}).then(db => {
const envCollection = db.collection('Environment');
const promises = envName.map(value => new Promise((resolve, reject) => {
envCollection.find({ environmentName: value }, (error, data) => {
if (error) return reject(error);
data.toArray((error, content) => {
if (error) reject(error);
else resolve(content);
});
});
}));
return Promise.all(promises);
}).then(contents => {
const ans = contents.reduce(
(ans, content) => content.reduce(
(ans, doc) => ans.add(doc.destination),
ans
),
new Set()
);
return [...ans];
});
}
主要区别在于:
DataBase.initDB()
包装在另一个Promise
中,因为结果取决于db
getDestination()
返回承诺链[...ans]
返回.then()
,而不是将其传递给回调.catch()
并让呼叫者处理所有错误请注意,由于我们是return Promises.all(promises)
的{{1}},因此我们可以从外部承诺获得.then()
,而不是链接到内部contents
。这是承诺可以摆脱Set
的主要好处之一。
最后,如果您想使用callback hell / async
而不是Promise.all()
,可以像这样再次重写它:
.then()
这里的主要区别是每个
static async getDestination (envName) {
const db = await new Promise(resolve => {
DataBase.initDB(resolve);
});
const envCollection = db.collection('Environment');
const promises = envName.map(value => new Promise((resolve, reject) => {
envCollection.find({ environmentName: value }, (error, data) => {
if (error) return reject(error);
data.toArray((error, content) => {
if (error) reject(error);
else resolve(content);
});
});
}));
const contents = await Promise.all(promises);
const ans = contents.reduce(
(ans, content) => content.reduce(
(ans, doc) => ans.add(doc.destination),
ans
),
new Set()
);
return [...ans];
}
替换为
return somePromise.then(someVariable => { ... });
P.S。如果要从const someVariable = await somePromise;
...
函数中删除构造的promise,可以将代码重构为两个可以这样使用的辅助函数:
getDestination()
它可能比以前的代码要多一些,但是现在您有了两个可重用的函数,除了static getCollection (collectionName) {
return new Promise(resolve => {
DataBase.initDB(resolve);
}).then(
db => db.collection(collectionName)
);
}
static getDocuments (collection, ...args) {
return new Promise((resolve, reject) => {
collection.find(...args, (error, data) => {
if (error) return reject(error);
data.toArray((error, content) => {
if (error) reject(error);
else resolve(content);
});
});
});
}
static async getDestination (envName) {
const envCollection = await this.getCollection('Environment');
const promises = envName.map(
environmentName => this.getDocuments(
envCollection,
{ environmentName }
)
);
const contents = await Promise.all(promises);
const ans = contents.reduce(
(ans, content) => content.reduce(
(ans, doc) => ans.add(doc.destination),
ans
),
new Set()
);
return [...ans];
}
之外,还可以从其他类型的查询中调用它们。