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 包含 leanObjectundefined 的数组,具体取决于 findOne 调用是否返回任何内容以及上面带有 if 的代码是否已运行` 当然。 Promise.all 将解析数组中传递给它的每个承诺链的结果数组(有关更多信息,请查看 MDN)。在此示例中,我们将一组 promise 传递给 Promise.all,其中每个 promise 都是一个 promise 链,由对 findOne 的调用组成,然后返回 leanObj,如果 result 为真,或者什么都没有,默认情况下是 undefined,如果 result 是假的。这意味着这些单独的承诺链中的任何一个都将使用leanObjundefined 解决。因此,每个承诺的结果数组将包含这两个值之一。 出现错误:TypeError: keys.map(...).then is not a function,并通过将then 带到外面解决。

以上是关于forEach 循环中的异步 findOne() 操作的主要内容,如果未能解决你的问题,请参考以下文章

ForEach 循环中的异步/等待节点 Postgres 查询

@foreach 循环中的多种形式。如何使用 javascript 异步提交一个。 C# 核心剃刀

异步 Node.js 循环中的变量范围

使 Foreach 异步

如何等待来自 forEach 循环的多个异步调用?

执行异步forEach循环后重新定义全局变量[重复]