jlrolin
jlrolin

Reputation: 1614

AWS Lambda and Promises: Callbacks not being called

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.

Upvotes: 1

Views: 426

Answers (2)

@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.

Upvotes: 1

Bergi
Bergi

Reputation: 664513

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.

Upvotes: 6

Related Questions