Search code examples
postgresqlexpresspg-promise

pg-promise - Not resolving multiple queries


I'm in the process of creating a mass mailer that allows a company to send out an email template to a specific plan that contains X amount of subscribers.

However, when using pg-promise, I can't figure how to loop through an array that contains one or more unique plan names that'll then be used as query parameters for subsequent queries to gather a single array of results. The subsequent queries don't resolve before express sends back a specified error message.

For example:

const selectedTemplate = await db.oneOrNone("SELECT * FROM templates WHERE userid=$1 and uniqueTemplateName=$2", [req.session.id, req.body.uniquetemplatename])

returns a JSON object...

{ ...
  plans : [ 'Unique Plan 1', 'Unique Plan 2', 'Unique Plan 3'...],
  ...
}

I then want to loop through this plans array to gather some subscriber emails. Since the selectedTemplate's plans array can contain a single "Unique Plan" or multiple, I'm trying to figure out how to get this query to work:

let subscriberEmails = [];

await each(selectedTemplate.plans, async plan => {
  const emails = await db.any("SELECT email FROM subscribers WHERE userid=$1 AND planName=$2", [req.session.id, plan])          
  each(emails, ({email}) => subscriberEmails.push(email))           
  console.log('Found emails: ', subscriberEmails);
})

console.log('Selected emails: ', subscriberEmails);
res.status(500).json({ message: "Route not configured yet" })

The problem I'm having is that Express isn't waiting for the pg-promise to resolve. So it's triggering my 500 server "Route not configured yet" message, then resolves the queries:

14:48:33 SELECT * FROM templates WHERE userid='12345' and uniqueTemplateName='example-unique-template'

        selectedTemplate: anonymous {
          id: '84efa448-b149-11e8-a7fd-3b9e4e9e5ece',
          key: 9,
          userid: '12345',
          status: 'active',
          fromsender: 'example@helpdesk.com',
          subject: 'Thank you for using our services!',
          templatename: 'Example Unique Template',
          uniquetemplatename: 'example-unique-template',
          message: '<h3>This is a test!</h3>',
          plans: [ 'Unique Plan 1', 'Unique Plan 2' ] 
        }

        Selected emails: []

POST /api/messages/create 500 28 - 34.125 ms

14:48:33 SELECT email FROM subscribers WHERE userid='12345' AND planName='Unique Plan 1'

        Found emails:  [ 
         'betatester19@example.com',
         'betatester20@example.com',
         'betatester21@example.com'
        ]

14:48:34 SELECT email FROM subscribers WHERE userid='12345' AND planName='Unique Plan 2'

        Found emails:  [ 
         'betatester19@example.com',
         'betatester20@example.com',
         'betatester21@example.com',
         'betatester1@example.com',
         'betatester2@example.com',
         'betatester3@example.com',
         'betatester4@example.com',
         'betatester5@example.com',
         'betatester6@example.com',
         'betatester7@example.com',
         'betatester8@example.com',
         'betatester9@example.com',
         'betatester10@example.com',
         'betatester11@example.com',
         'betatester12@example.com',
         'betatester13@example.com',
         'betatester14@example.com',
         'betatester15@example.com',
         'betatester16@example.com',
         'betatester17@example.com',
         'betatester18@example.com' 
        ]

The function as is:

async (req, res, done) => {
    if (!req.body) {
      return res.status(404).json({ message: "Missing query parameters" });
      done();
    }

    try {
      const selectedTemplate = await db.oneOrNone("SELECT * FROM templates WHERE userid=$1 and uniqueTemplateName=$2", [req.session.id, uniquetemplatename]);
      if (!selectedTemplate) {
        return res.status(404).json({ message: "Unable to locate selected template" });
        done();
      }
      console.log('selectedTemplate', selectedTemplate);

      let subscriberEmails = [];
      await each(selectedTemplate.plans, async plan => {
        const emails = await db.any("SELECT email FROM subscribers WHERE userid=$1 AND planName=$2", [req.session.id, plan])
        each(emails, ({email}) => subscriberEmails.push(email))
        console.log('Found emails: ', subscriberEmails);
      })

      console.log('Selected emails: ', subscriberEmails); 
      res.status(500).json({ message: "Route not configured yet" })
    } catch (err) { res.status(500).json({ message: err }); done(); }
  }

A vastly simplified DB structure:

├── Template
│   └── Plans (references an array of unique plan name(s))
|
|── Plan
|   └── PlanName (unique plan name)
|
│── Subscriber
|   └── PlanName (references a single unique plan name)
|   └── Email (unique email)

I've tried using a db.task(), but again, the queries didn't resolve before Express sent back the message.


Solution

  • Figured out where the problem was. Just in case anyone else stumbles upon this predicament: Functions like "map" and "each" don't handle async/await. They instead just return a pending Promise. Therefore, you'll want to use a library like Bluebird, which implements Promise-based each/map/etc functions (found under "Collections").

    The fix for the above to work:

    try {
      await db.task('create-message', async t => {
        const selectedTemplate = await t.oneOrNone(findTemplateByName, [req.session.id, uniquetemplatename])
        if (!selectedTemplate) return sendError(unableToLocate('template'), res, done);
        const { fromsender, subject, message, plans, templatename } = selectedTemplate;
    
        let subscriberEmails = [];
        await Promise.each(plans, async plan => {
            const emails = await t.any(getAllEmailsByPlan, [req.session.id, plan]);
            each(emails, ({email}) => subscriberEmails.push(email));
        })
    
        if (isEmpty(subscriberEmails)) return sendError(unableToLocate('subscribers in the selected plan(s)'), res, done);
    
        const msg = {
            to: subscriberEmails,
            from: fromsender,
            replyTo: fromsender,
            subject: subject,
            html: message
        };
    
        await mailer.sendMultiple(msg);
    
        await t.none(createMessageTransaction, [req.session.id, templatename, fromsender, subject, currentDate, plans]);
    
        await t.none(createNotification, [req.session.id, 'mail_outline', `The following template: ${templatename} has been sent out to the subscibers in the following ${plans.length > 1 ? "plans" : "plan"}: ${plans}.`, date]);
    
        return res.status(201).send(null);
      })
    } catch (err) { return sendError(err, res, done); }