【问题标题】:Sending emails in bulk and catching unsuccessful attempts批量发送电子邮件并捕获不成功的尝试
【发布时间】:2019-09-16 19:47:51
【问题描述】:

前端代码包含一个名称列表,每个名称旁边都有复选框。目标是向所有被检查的姓名发送电子邮件。单击提交按钮时,会向我的后端发送一组 ID(针对每个用户)。

后端代码查询数据库(mongo 使用 mongoose odm)并找到用户。我需要在后端完成一些任务:

  • 使用提供的 ID 数组查找用户
  • 创建并向每个用户发送电子邮件
  • 如果电子邮件发送成功,请更新数据库中用于发送电子邮件的文档字段
  • 如果邮件失败,将用户名发回前端,通知“发件人”邮件尝试失败。

我在这段代码上工作的时间比我想承认的要长...这是我目前所拥有的(我担心后端代码):

exports.sendEmailToUsers = function (req, res, next) {
  mongoose.model('SpendingReport').find({ _id: { $in: req.body.recipientIds } }).populate('report user')
    .find({ 'report.emailedReport': { $exists: false } })  // this needs to be refined for dev, new reports will have an emailedGradePost property
    .then(spendingReports => {
      return Bluebird.map(spendingReports, spendingReport => {
        const email = new Email({ email: spendingReport.user.email, name: spendingReport.user.fullName }, {})

        return email.send()
          .then(() => {
            spendingReport.report.update({ emailedReport: new Date() })
            // I don't need anything returned if it is successful, this feels weird though, map doesn't
            // seem like the  correct function to use.

            // return {spendingReport.report.emailedGradePost}
          })
          .catch(e => {
            // I am catching each email's error so I know which email failed
            return { error: e, user: spendingReport.user.fullName }
          });
      });
    })
    .then(unsuccessfulAttempts => {
      // the array has the error obect from the .catch and also undefined values for the successful attempts
      console.log(unsuccessfulAttempts);
    })
    .then(() => {
      res.sendStatus(200); //  filler status for now
    })
    .catch(e => {
      console.log(e);
    });
};

这是我的问题:

  • 我正在使用Bluebird.map,这感觉就像代码的味道。理论上,我可以在包含来自数据库的一组文档的spendingReports 数组上使用.map,并使用来自每个spendingReport 的信息创建一封电子邮件。问题是,当我将电子邮件返回到承诺链中的下一个 .then() 时,我将无法访问 spendingReport 对象,例如
exports.sendEmailToUsers = function (req, res, next) {
  mongoose.model('SpendingReport').find({ _id: { $in: req.body.recipientIds } }).populate('report user')
    .find({ 'report.emailedReport': { $exists: false } })  // this needs to be refined for dev, new reports will have an emailedGradePost property
    .then(spendingReports => {
      return spendingReports.map(spendingReport => new Email({ email: spendingReport.user.email, name: spendingReport.user.fullName }, {}));
      // {email: email, spendingReport: spendingReport} I might need this format instead, referenect the note
      // in the next promise chain.
    })
    .then(emails => {
      return Bluebird.map(emails, email => {
        email.send()
          .then(() => {
            // Note: I lost access to "spendingReport", I would need to pass this object
            // with each email object {email: email, spendingReport: spendingReport}
            spendingReport.report.update({ emailedReport: new Date() })
              .catch(e => {
                return { error: e, user: spendingReport.user.fullName };
              })
          })
      })
    })
    .then(unsuccessfulAttempts => {

      console.log(unsuccessfulAttempts);
    })
    .then(() => {
      res.sendStatus(200); //  filler status for now
    })
    .catch(e => {
      console.log(e);
    });
};
  • 我有一个嵌套的承诺链(在Bluebird.map 内,发送电子邮件,然后将其保存到成功的数据库中)。我知道嵌套承诺是一种反模式。减轻嵌套承诺的唯一方法是在每个.then 中传递与每封电子邮件关联的文档对象,与在Bluebird.map 中仅具有嵌套承诺链相比,这感觉更像是一种负担

    李>
  • 当电子邮件成功并成功保存时,我不知道在Bluebird.map 中返回什么。现在我不返回任何东西,所以 undefined 被返回。

  • 理想情况下,我可以并行发送所有电子邮件,例如Promise.all([email.send(), email.send(), email.send()]),但是,这使得将电子邮件成功保存到数据库更具挑战性(我需要再次访问spendingReports再次文档并更新report,这感觉就像很多查询)。

【问题讨论】:

  • 您需要返回spendingReport.report.update 承诺或其中的then() 将立即解决

标签: javascript mongodb promise


【解决方案1】:

使用 async-await 可以减少您的问题(因为您可以通过索引获取所有项目)

async function(req, res, next) {
  let spendingReports = await mongoose.model('SpendingReport').find(...)
  let emails = spendingReports.map(r=>new Email(...))
  let sendingmails = emails.map(e=>e.send())
  let success=[],fail=[];
  await Promise.all(sendingmails.map((s,i)=>s.then(_=>success.push(i)).cache(_=>fail.push(i))))

  //now you have index of success and failed mails. 
  //just process these data and do whatever you want
}

中间数据不是必须的,比如这个单行(不要真的这样做)

async function(req, res, next) {
  let success=[],fail=[];
  await Promise.all(await mongoose.model('SpendingReport').find(...).then(spendingReports => spendingReports.map(r=>(new Email(...)).send().then(_=>success.push(r)).cache(_=>fail.push(r))))

  //now you have success and failed spendingReports. 
  //just process these data and do whatever you want
}

【讨论】:

  • 哈,我只是想在评论中提出这个建议。很好的答案。
  • 不幸的是项目所在的节点版本不支持异步等待,不过我喜欢这个解决方案!
猜你喜欢
  • 2016-03-25
  • 1970-01-01
  • 1970-01-01
  • 2015-05-14
  • 2019-01-13
  • 2011-03-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多