Brian
Brian

Reputation: 4344

Firebase cloud function promises

I'm having a hard time getting a promise chain to flow correctly in a firebase cloud function. It loops through a ref and returns an array of emails for sending out notifications. It has some nested children, and I think that's where I'm going wrong, but I can't seem to find the error.

/courses structure

{
  123456id: {
    ..
    members: {
      uidKey: {
        email: [email protected]
      },
      uidKey2: {
        email: [email protected]
      }
    }
   },
   some12345string: {
     // no members here, so skip
   }
}

function.js

exports.reminderEmail = functions.https.onRequest((req, res) => {
  const currentTime = new Date().getTime();
  const future = currentTime + 172800000;
  const emails = [];

  // get the main ref and grab a snapshot
  ref.child('courses/').once('value').then(snap => {
    snap.forEach(child => {
      var el = child.val();

      // check for the 'members' child, skip if absent
      if(el.hasOwnProperty('members')) {

        // open at the ref and get a snapshot
        var membersRef = admin.database().ref('courses/' + child.key + '/members/').once('value').then(childSnap => {
          console.log(childSnap.val())
          childSnap.forEach(member => {
            var email = member.val().email;
            console.log(email); // logs fine, but because it's async, after the .then() below for some reason
            emails.push(email);
          })
        })
      }
    })
    return emails  // the array
  })
  .then(emails => {
    console.log('Sending to: ' + emails.join());
    const mailOpts = {
      from: '[email protected]',
      bcc: emails.join(),
      subject: 'Upcoming Registrations',
      text: 'Something about registering here.'
    }
    return mailTransport.sendMail(mailOpts).then(() => {
      res.send('Email sent')
    }).catch(error => {
      res.send(error)
    })
  })
})

Upvotes: 0

Views: 1240

Answers (2)

Renaud Tarnec
Renaud Tarnec

Reputation: 83093

The following should do the trick.

As explained by Doug Stevenson in his answer, the Promise.all() method returns a single promise that resolves when all of the promises, returned by the once() method and pushed to the promises array, have resolved.

exports.reminderEmail = functions.https.onRequest((req, res) => {
  const currentTime = new Date().getTime();
  const future = currentTime + 172800000;
  const emails = [];

  // get the main ref and grab a snapshot
  return ref.child('courses/').once('value') //Note the return here
  .then(snap => {
    const promises = [];

    snap.forEach(child => {      
      var el = child.val();

      // check for the 'members' child, skip if absent
      if(el.hasOwnProperty('members')) { 
        promises.push(admin.database().ref('courses/' + child.key + '/members/').once('value')); 
      }
    });

    return Promise.all(promises);
  })
  .then(results => {
    const emails = [];

    results.forEach(dataSnapshot => {
       var email = dataSnapshot.val().email;
       emails.push(email);
    });

    console.log('Sending to: ' + emails.join());
    const mailOpts = {
      from: '[email protected]',
      bcc: emails.join(),
      subject: 'Upcoming Registrations',
      text: 'Something about registering here.'
    }
    return mailTransport.sendMail(mailOpts);
  })
  .then(() => {
      res.send('Email sent')
    })
  .catch(error => { //Move the catch at the end of the promise chain
      res.status(500).send(error)
  });

})

Upvotes: 1

Doug Stevenson
Doug Stevenson

Reputation: 317467

Instead of dealing with all the promises from the inner query inline and pushing the emails into an array, you should be pushing the promises into an array and using Promise.all() to wait until they're all done. Then, iterate the array of snapshots to build the array of emails.

You may find my tutorials on working with promises in Cloud Functions to be helpful in learning some of the techniques: https://firebase.google.com/docs/functions/video-series/

Upvotes: 1

Related Questions