我正在学习 Node ,我有一段代码可以查询数据库中的数据,我想将其传递给回调函数。但我只想返回唯一的成员。
这意味着我必须遍历查询结果,并将唯一成员推送到数组中,然后将该数组传递到 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));
但是我无法理解应该在哪笔交易上创建 promise 。因为如果我在最内层循环中创建 Promise,则循环外部的 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));
});
}
最佳答案
回调
以下是创建 Promise 数组并使用它来聚合 ans
的方法。然后调用你的cb()
一次。我还建议使用the Node.js callback convention的cb(error, result)
而不是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);
});
});
}
请注意,每个 new Promise()
同步构造,并且resolve()
或reject()
被异步调用。这是必要的,因为您的数据库查询尚未返回 Promise
.
如果您使用返回 Promise 的 API,则应避免 explicit promise construction antipattern并从现有的 promise 中链接出来。
我还优化了 ans
的聚合通过使用 Set
而不是数组,因此我们实际上可以省略对现有值的检查,因为与数组不同,集合只保留每个唯一值之一。
promise 链
如果你想要你的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()
返回 promise 链 - 返回
[...ans]
来自.then()
而不是将其传递给回调 - 删除
.catch()
并让调用者处理任何错误
请注意,因为我们 return Promises.all(promises)
来自.then()
,我们可以得到contents
来自外部 promise ,而不是链接到内部 Promise.all()
。这是 promise 提供逃离 callback hell 的主要好处之一.
异步/等待
最后,如果您想使用 async
/ await
而不是.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 => { ... });
被替换为
const someVariable = await somePromise;
...
模块化
附注如果你想从 getDestination()
中删除构造的 promise 函数,您可以将代码重构为两个辅助函数,可以像这样使用:
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];
}
它可能比以前稍微多一些代码,但是现在您有两个可重用的函数,可以从除 getDestination()
之外的其他类型的查询中调用。 .
关于node.js - 理解和实现 Promise 和异步事物如何与循环一起工作存在问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57118724/