forEach 循环中的异步 findOne() 操作
Posted
技术标签:
【中文标题】forEach 循环中的异步 findOne() 操作【英文标题】:Async findOne() operation inside forEach Loop 【发布时间】:2017-11-03 08:37:19 【问题描述】:我很难理解 javascript Promises。我正在我的 Mongoose 模型中搜索满足特定条件的对象,如果它们存在,我想将该对象变成一个普通的 JS 对象并在其上添加一个属性。
不幸的是,我无法弄清楚如何确保我的forEach
循环在我的承诺最终解决之前完全运行。请看我的代码。
// Called to check whether a user has participated in a given list of challenges
participationSchema.statics.getParticipation = function(user, challenges)
return new Promise((resolve, reject) =>
challengesArray = [];
challenges.forEach((challenge) =>
// Model#findOne() is Async--how to ensure all these complete before promise is resolved?
Participation.findOne(user, challenge)
.then((res) =>
if (res)
var leanObj = challenge.toObject();
leanObj.participation = true;
challengesArray.push(leanObj);
)
.catch(e => reject(e));
)
console.log("CHALLENGES ARRAY", challengesArray); // Challenges Array empty :(
resolve(challengesArray);
);
我查看了类似的问题,但无法得到答案。感谢您的帮助。
【问题讨论】:
避免使用Promise
constructor antipattern,不要使用forEach
!您正在寻找Promise.all
好吧,因为异步代码是异步的,你显然在异步结果可用之前解析challengesArray
【参考方案1】:
所以,当您调用 getParticipation
时发生的情况是,forEach
循环一直运行,并且所有针对 Participation.findOne
的单独承诺已创建但尚未解决。执行不会等待他们解决并在forEach
之后继续执行,解决***承诺challengesArray
,此时仍为空。一段时间后,forEach
中创建的 Promise 开始解析,但它们的结果现在丢失了。
另外,正如 Bergi 在 cmets 中提到的,嵌套 promise 被认为是 anti-pattern;承诺应该是链式的,而不是嵌套的。
你想要的是使用Promise.all
之类的东西来等待你所有的承诺首先完成,然后你过滤掉所有不存在的结果,最后返回数组。
participationSchema.statics.getParticipation = function(user, challenges)
return Promise.all(challenges.map(challenge =>
return Participation.findOne(user, challenge).then(result =>
if (result)
var leanObj = challenge.toObject();
leanObj.participation = true;
return leanObj;
);
)
// at this point, results contains an array of `leanObject` and `undefined` depending if the `findOne` call returned anything and the code withing the `if` above was run
.then((results) =>
return results.filter(result => !!result) // filter out `undefined` results so we only end up with lean objects
);
【讨论】:
啊,这太有趣了!所以在这个设计中,你准备了一个数组中的所有承诺,promises.all()
确保它们全部完成,对吗?请让我快速尝试一下,然后回复您。
不确定您所说的准备是什么意思。发生的事情是,首先,挑战数组被转换为一组承诺,代表对Participation.findOne
的调用,并且所有请求基本上是并行进行的。然后这些请求开始以任意顺序完成,promise 开始相应地解决,它为每个请求运行then
子句并检查结果是否存在,如果存在则将它们转换为leanObj
。一旦所有这些承诺都得到解决,Promise.all
就会使用从每个承诺中获得的结果数组来解决。
啊,是的,这就是我所说的准备,就像创建一堆承诺一样。您还可以了解 results 包含 leanObject
和 undefined
的数组,具体取决于 findOne
调用是否返回任何内容以及上面带有 if
的代码是否已运行`
当然。 Promise.all
将解析数组中传递给它的每个承诺链的结果数组(有关更多信息,请查看 MDN)。在此示例中,我们将一组 promise 传递给 Promise.all
,其中每个 promise 都是一个 promise 链,由对 findOne
的调用组成,然后返回 leanObj
,如果 result
为真,或者什么都没有,默认情况下是 undefined
,如果 result
是假的。这意味着这些单独的承诺链中的任何一个都将使用leanObj
或undefined
解决。因此,每个承诺的结果数组将包含这两个值之一。
出现错误:TypeError: keys.map(...).then is not a function
,并通过将then
带到外面解决。以上是关于forEach 循环中的异步 findOne() 操作的主要内容,如果未能解决你的问题,请参考以下文章
ForEach 循环中的异步/等待节点 Postgres 查询