1
votes

I'm assuming that I'm lacking some fundamentals on promises. I have a process that is within a AWS Lambda that downloads three files, then produces an output that is sent via email.

module.exports.test = async (event) => {
   var p = download1();
   var c = download2();
   var h = download3();

   await Promise.all([p, c, h]).then(() => {
     ... bunch of logic manipulating the data
     customers.forEach(i => {
       buildFile().then(data => {
         sendEmail(data).then(response => {
            console.log('Email sent successfully');
         });
       });
     });
   }, errHandler);
};

Both the buildFile and sendEmail functions return a Promise, but I never get to the 'Email sent successfully' message. It runs the code, but never actually returns before the Lambda completes (at least that's what I think is happening).

My understanding was that the Promise would fulfill the callback, but now I'm thinking I need to do something similar to how I did the downloads within the original Promise.all(). Is that the right direction?

The process should get the files, then it loops through customers to create each file and send via SES.

2
what if you try it without await - KB_
customers.forEach iterates the customers and for each one it creates a promise but you don't wait for it to be fulfilled. Save these promises into an array and run Promise.all and them and wait till they're done. - Nir Alfasi
As @alfasin pointed out, you are racing. But, in addition, you should use .catch() for EACH promise chain so that you can catch errors that may be created within the chain. - Randy Casburn

2 Answers

6
votes

You're looking for

module.exports.test = async (event) => {
  var p = download1();
  var c = download2();
  var h = download3();

  try {
    await Promise.all([p, c, h]);

    // ... bunch of logic manipulating the data
    var promises = customers.map(async (i) => {
      var data = await buildFile();
      var response = await sendEmail(data);
      console.log('Email sent successfully');
    });
    await Promise.all(promises);
  } catch(e) {
    errHandler(e);
  }
};

Your test function didn't wait for the promises that you created in the forEach loop, so the lambda completes before everything is done.

1
votes

@Bergi answer is correct, however I would like to expand it a little bit and give you some resources to increase or strength your Promises knowledge. I'll use the next snippet of code, it's a bit cumbersome because I wrote it on Google Chrome snippets so feel free to paste it there and play around with it:

(function() {
    const promise1 = new Promise(function(resolve, reject) {
        setTimeout(function() {
            resolve('Replicant');
        }, 300);
    });

    const promise2 = new Promise(function(resolve, reject) {
        setTimeout(function() {
            resolve('Human?');
        }, 300);
    });

    function buildFile(type) {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve(`${type}`);
            }, 300);
        });
    }

    function sendMail(customer, answer) {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve(`Sent test to: ${customer}, he/she is a ${answer}`);
            }, 300);
        });
    }

    let customers = ['Rob Batty', 'Rachel', 'Deckard'];

    async function myFunc() {

        const [a, b, c] = await Promise.all([promise1, promise1, promise2]);
        const answers = [a, b, c];

//         const promises = customers.map(async (name, index) => {
//             const file = await buildFile(answers[index]);
//             const mail = await sendMail(name, file);
//             console.log(mail);
//         });

        const promises = customers.map((name, index) => {
            buildFile(answers[index])
                .then(file => sendMail(name, file))
                .then(sent => console.log(sent))
                // If you're going to use Promises this is important! :D
                .catch(err => console.log(err))
        });

        const [m1, m2, m3] = await Promise.all(promises);

        console.log(m1, m2, m3);
    }

    myFunc();
})()

As pointed out in the answer, the problem is related to the use of forEach, why? Well, simply because you're executing asynchronous code on a synchronous type of method, the don't get along very well :), so, the solution is to create an Array of Promises, like a Factory. After the map function the Promises are Pending nor Fullfiled or Rejected it's when you call the Promise.all() method that you await for their results and they give you the values or in your use case, generate the file and then send the email to the user. I hope this helps you to get a better understanding on how Promises works. I'll leave at the end two very important links, at least for me, that helped me out at some point with Promises. Cheers, sigfried.