批量发送电子邮件并捕获失败的尝试

时间:2019-04-27 15:36:01

标签: javascript mongodb promise

前端代码包含一个名称列表,每个名称旁边带有复选框。目的是让电子邮件发送到所有已检查的名称。单击提交按钮后,(每个用户的)ID数组将发送到我的后端。

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

  • 使用提供的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,感觉就像是代码的味道。从理论上讲,我可以只在.map数组上使用spendingReports,该数组包含来自数据库的一系列文档,并使用每个spendingReport的信息创建一封电子邮件。问题是,当我将电子邮件退回至承诺链中的下一个spendingReport时,我将失去对.then()对象的访问权限,例如
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);
    });
};
  • 我有一个嵌套的Promise链(在Bluebird.map内部,已发送电子邮件,然后将其成功保存到数据库中)。我知道嵌套的承诺是一种反模式。减轻嵌套承诺的唯一方法是传递与每个.then中的每封电子邮件相关的文档对象,与仅在Bluebird.map中嵌套嵌套的诺言链相比,这感觉负担更大。 p>

  • 我不知道成功发送电子邮件并成功保存后在Bluebird.map中返回什么。目前,我什么也没返回,所以返回了undefined

  • 理想情况下,我可以并行发送所有电子邮件,例如Promise.all([email.send(), email.send(), email.send()]),但是,这使得向数据库保存电子邮件成功的难度更大(我需要再次访问{ {1}}再次进行文档编制并更新spendingReports,感觉就像很多查询一样。

1 个答案:

答案 0 :(得分:3)

使用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
}