【问题标题】:How to wait for subquery results in node js?如何在节点 js 中等待子查询结果?
【发布时间】:2020-03-07 19:07:15
【问题描述】:

我将 Graphql 与 MongoDB 一起使用。在解析器中使用了子查询,但在执行子查询时主查询返回数据不等到子查询完成。 我想在解析器中使用子查询参数来操作主查询。

  return  await Articles.find({ Status: 1, isPublish : true  })
      .sort({TotalClapCount:-1})
      .sort({ViewCount:-1})
      .skip( offset )
      .limit(limit)
      .then( async ( lor ) => { await
       lor.forEach(async function(data, key){
          data["isBookmark"] =  
                  await ArticleBookmarks
                  .find({ ArticleID : data["ID"], UserID : ArgsUserID, Status : 1 })
                  .countDocuments()
                  .then( (hre) =>{return (hre == 1) ? true : false; });
      );
    });
    return  lor;
});

我想在单个查询中显示带有书签的文章列表,但 return lor 在子查询操作之前执行。 async-await 如何为此工作?

【问题讨论】:

  • 如果您使用适当的缩进格式化您的代码,我们将有更好的机会遵循它正在尝试做的事情。
  • 因此,.forEach() 不会等待返回的承诺,因此其回调中的 await 不会做任何有用的事情。更改为常规的for 循环,您将有更好的机会。如果不将代码复制到编辑器中并正确格式化,我真的不知道它还试图做什么。
  • @RatanUdayKumar - 您的解决方案有多个问题。它不会起作用。
  • @jfriend00 您可以发布您的解决方案。并希望对调查问卷有所帮助。
  • 请检查我的解决方案。希望它会工作

标签: node.js mongodb express async-await graphql


【解决方案1】:

尝试如下

let MainFunction = () => {
    return new Promise(async (resolve, reject) => {
        try {
            let async = require("async");
            let query = {
                Status: 1,
                isPublish: true
            }
            let sortOptions = {
                TotalClapCount: -1,
                ViewCount: -1
            }
            let Data = await Articles.find(query).sort(sortOptions).skip(offset).limit(limit).lean();
            async.eachSeries(Data, async (data, callback) => {
                try {
                    let cquery = {
                        ArticleID: data["ID"],
                        UserID: ArgsUserID, Status: 1
                    };
                    let countedArticles = await ArticleBookmarks.countDocuments(cquery).lean();
                    data.isBookmark = (countedArticles >= 1) ? true : false;
                    callback();
                } catch (error) {
                    callback(error);
                }
            }, async (err) => {
                if (err) reject(err);
                resolve(Data);
            });
        } catch (error) {
            console.error(error);
            reject(error);
        }
    });
}


return await MainFunction();

注意:请根据需要在 MainFunction 中传递必要的参数。

【讨论】:

  • 你不应该围绕现有的承诺包装一个新的承诺。这是一个承诺反模式。正如我建议 OP,只需将 .forEach() 切换到常规 for 循环,然后 await 将停止循环。加上await Data.forEach() 没有任何用处,因为.forEach() 没有返回值。而.forEach() 循环内的await 不会导致循环停止。此代码不能解决任何问题。
  • 我已经更新了我的解决方案。它一定会奏效的。
  • 哇。请注意 OP,这确实不是使用 Promise 进行编程的最佳方式。当我使用可以编写代码的计算机时,我将向您展示一种更好的方法。正如我之前所说,将现有的 Promise 包装在一个新的手动构造的 Promise 中被认为是一种反模式。而且,通过正确使用for 循环和async/await,完全不需要async.series 库函数。
  • 使用异步库,问卷可能会从异步库中受益,对他未来的复杂功能很有用,问卷可以顺利使用。
  • @RatanUdayKumar 谢谢,上面的解决方案工作正常。
【解决方案2】:

您可以像这样使用常规的 Promise、常规的 for 循环和 async/await。这里不需要async.series()这样的外部库函数:

let lor = await Articles.find({ Status: 1, isPublish: true})
    .sort({TotalClapCount: -1})
    .sort({ViewCount: -1})
    .skip(offset)
    .limit(limit);

for (let data of lor) {
    let hre = await ArticleBookmarks.find({
        ArticleID: data.ID,
        UserID: ArgsUserID,
        Status: 1
    }).countDocuments();
    data.isBookmark = (hre == 1);
}
return lor;

变化:

  1. return await Articles.find() 没有任何用处,因为它与 return Articles.find() 没有什么不同,因为两者都只返回一个承诺,因为它位于 async 函数内,并且所有 async 函数都返回一个承诺。

  2. .forEach() 更改为常规for 循环,因为for 循环将暂停await 函数的执行。 forEach() 不会暂停循环。

  3. await xxx.forEach() 没有 await 任何东西,因为 .forEach() 没有返回值,而 await 只有在你 await 一个承诺时才会做一些有用的事情。

  4. (hre == 1) ? true : false; 更改为(hre == 1),因为hre == 1 已经是一个布尔值,所以不需要? true : false。就个人而言,如果您知道数据实际上是数字而不是字符串,我可能会使用 (hre === 1),因为 === 几乎总是比 == 更可取(没有隐式或意外的类型转换)。

    李> 1234563 /p>

其他cmets:

  1. await 只应在您等待承诺时使用。那是唯一一次做有用的事情。

  2. new Promise() 包裹在其他promise 返回函数周围被认为是promise anti-pattern,因为它不是必需的,并且会为编程错误创造许多机会,尤其是在错误处理方面。您可以直接返回并使用您已经拥有的承诺,而无需包装新的承诺。

  3. async.eachSeries() 没有提供任何使用 async/await 进行常规 Promise 排序的功能,除非您尝试在没有 async/await 的环境中运行。

  4. 当您希望 await 暂停循环时,请使用常规的 for 循环。它不会暂停 .map().forEach() 等数组方法的循环,因为这些迭代方法不支持承诺。他们不会在回调返回的承诺上暂停(当您将回调声明为 async 时会发生这种情况)。


并行查询的另一种可能性

由于循环的一个迭代的处理独立于其他迭代,您可以并行运行所有这些数据库查询并使用Promise.all() 知道它们何时完成。这有一个缺点,如果数组很大,您将一次向数据库抛出大量查询。好处是,如果数据库没有被同时查询的数量压得喘不过气来,端到端的处理时间可能会更短。你可以这样做:

let lor = await Articles.find({ Status: 1, isPublish: true})
    .sort({TotalClapCount: -1})
    .sort({ViewCount: -1})
    .skip(offset)
    .limit(limit);

await Promise.all(lor.map(async data => {
    let hre =  await ArticleBookmarks.find({
        ArticleID: data.ID,
        UserID: ArgsUserID,
        Status: 1
    }).countDocuments();
    data.isBookmark = (hre == 1);
});
return lor;

【讨论】:

  • 我仍然认为混合.thenawait 并不是最好的。应该更简单。
  • @Ashh - 是的,我同意。我简化了我的答案。
【解决方案3】:

forEach() 不会等待异步操作完成。您可以使用内部带有await 的for 循环。但是,缺点是其他运营商必须等待前一个运营商完成。适当的解决方案是使用Promise.all,它返回一个单一的 Promise,当所有作为迭代传递的 Promise 都已解决时,该 Promise 将解决。希望这会有所帮助。

【讨论】:

  • 如果 OP 想要对他们的循环进行排序(这是他们似乎想要做的,并且可能有助于避免过多并行查询压倒数据库),那么就不需要 @987654325 @。请参阅我的答案以了解如何完成。
  • 在发布我的答案之前,我确实阅读了您的答案。我在作者的代码中没有看到任何理由来对他的循环进行排序。 “......可能有助于不要用太多并行查询压倒数据库”:我同意你的观点。但是,作者可能还希望进行并行查询以减少响应时间。好消息是我们提出了两种解决方案,哈哈!
  • 你的分数很好。我在答案中添加了并行版本的代码,并解释了与排序相比的相对优缺点。
猜你喜欢
  • 1970-01-01
  • 2021-08-15
  • 2017-10-30
  • 2020-03-10
  • 2012-10-28
  • 2019-04-02
  • 1970-01-01
  • 1970-01-01
  • 2014-03-03
相关资源
最近更新 更多