【问题标题】:NodeJS async. Header sent before Object is properly formedNodeJS 异步。在正确形成对象之前发送的标头
【发布时间】:2020-03-11 18:53:05
【问题描述】:

我在使用异步调用时遇到了麻烦。这是给我带来问题的代码sn-p

company.find({ managerID: req.session.manager.managerID })
        .exec((err, _companies) => {
            if(err)
                return res.status(500).send({ message: "Uh, oh! Something went wrong." });
            else {
                var data = [];
                var i = 0;
                _companies.forEach(c => {
                    user.find({ companyID: c._id, status: 0})
                    .then(_user => {
                            // console.log(_user);
                            var dummy = {
                                companyID: c._id,
                                companyName: c.companyName,
                                selectedCurrency: c.selectedCurrency,
                                metrics: c.metrics,
                                headAdmin: _user[0].email,
                                headName: _user[0].designer
                            };
                            console.log(dummy);
                            data.push(dummy);
                            console.log(data);
                            i++;
                    }).catch(err => {
                        return res.status(500).send({ message: "Uh, oh! Something went wrong." });
                    });
                });
                if(i == _companies.length)
                    return res.status(200).send({ data: data });
            }
        });

我的数据对象在对象正确形成之前被发送。

我尝试从 .exec 切换到 .then 承诺,希望它可以修复它,甚至尝试仅在循环完成后返回(甚至不返回空数组)。

每当我在return res.status(200).send({ data: data}) 之前调用console.log("hi") 时,它总是在console.log(dummy) 之前打印

等待解决这个问题,如果是,我应该如何使用它,因为我不熟悉如何使用它?

【问题讨论】:

    标签: node.js mongodb asynchronous mongoose


    【解决方案1】:

    Array.prototype.forEach 同步工作,因此它不会等待其中的异步操作,例如user.find()。因此,i == _companies.length 在异步操作完成之前计算为true

    正如另一个答案中所建议的,您可以通过将所有待处理的 Promise 收集到一个数组中并将其传递给 Promise.all() 来解决此问题。在您的场景中,这可能如下所示:

    company.find({ managerID: req.session.manager.managerID })
      .exec((err, _companies) => {
        if (err) {
          return res.status(500).send({ message: "Uh, oh! Something went wrong." });
        }
        const results = _companies.map(c =>
          user.find({ companyID: c._id, status: 0})
            .then(_user => ({
                companyID: c._id,
                companyName: c.companyName,
                selectedCurrency: c.selectedCurrency,
                metrics: c.metrics,
                headAdmin: _user[0].email,
                headName: _user[0].designer
            }))
        );
        return Promise.all(results)
          .then(data => res.status(200).send({ data: data }))
          .catch((err) => res.status(500).send({ message: "Uh, oh! Something went wrong." }));
      });
    

    【讨论】:

    • 这就是我一直在寻找的,因为我非常不熟悉如何使用 Promise。谢谢,效果很好!
    • @Yoshy 很高兴知道它对您有帮助,不客气!
    【解决方案2】:

    那是因为你的 forEach 在任何循环中都有异步指令。所以基本上 forEach 是同步的,但是你在这里插入了 Promise 并且你的下一条指令在 forEach 中的所有循环发生后执行

    如果你使用 Bluebird 承诺(顺序执行)

    Promise.mapSeries(_companies, c => /*generate data*/)
    .then(res.status(200).send({ data: data }))
    

    或者使用 Promise.all(并行执行)

    Promise.all(_companies.map(c => /*generate data*/)
    .then(res.status(200).send({ data: data }))
    

    【讨论】:

    • 我对 Bluebird 不熟悉,抱歉。如果可能的话,我想避免仅仅因为 1 个错误而下载新包,但我会考虑到这一点。谢谢。
    • 你可以用普通的 Promise 来做到这一点,async/await 可以帮助你的代码更干净,并在循环或映射中使用await,如:await Promise.all(_companies.map(c => /*promise expression*/))
    猜你喜欢
    • 2016-07-16
    • 1970-01-01
    • 2018-09-20
    • 1970-01-01
    • 1970-01-01
    • 2020-02-21
    • 2017-06-22
    • 2017-12-20
    • 1970-01-01
    相关资源
    最近更新 更多