【问题标题】:Async findOne() operation inside forEach LoopforEach 循环中的异步 findOne() 操作
【发布时间】: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

标签: javascript node.js asynchronous mongoose promise


【解决方案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 带到外面解决。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多