如何在节点 js 中等待子查询结果?

Posted

技术标签:

【中文标题】如何在节点 js 中等待子查询结果?【英文标题】:How to wait for subquery results in node 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 您可以发布您的解决方案。并希望对调查问卷有所帮助。 请检查我的解决方案。希望它会工作 【参考方案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;

变化:

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

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

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

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

    李> 1234563 /p>

其他cmets:

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

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

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

    当您希望 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 @。请参阅我的答案以了解如何完成。 在发布我的答案之前,我确实阅读了您的答案。我在作者的代码中没有看到任何理由来对他的循环进行排序。 “......可能有助于不要用太多并行查询压倒数据库”:我同意你的观点。但是,作者可能还希望进行并行查询以减少响应时间。好消息是我们提出了两种解决方案,哈哈! 你的分数很好。我在答案中添加了并行版本的代码,并解释了与排序相比的相对优缺点。

以上是关于如何在节点 js 中等待子查询结果?的主要内容,如果未能解决你的问题,请参考以下文章

沫沫金Sql查询树结构所有终极子节点

一个树结构的表查找没有子节点的节点,如何写SQL?

SQL (根据子节点查询父节点信息)

SQL递归查询所有子节点

200分求助!SQL递归查询所有子节点

如何在超级查询中使用子查询的结果?