8

I'm trying to use AWS lambda to test a few API calls using axios, however I'm having some trouble. Every post I came across said the best way to handle promises in Lambda was to use async/await rather than .then, so I made the switch. When I run the program using node it works perfectly, but when I invoke the Lambda locally, it seems like everything after the axios call is being skipped. When I invoke the Lambda locally without await, the calls after it run fine, but then I'm forced to use .then which the Lambda doesn't wait for anyway. I've increased the Lambda timeout to 900, and I've run sam build before sam invoke local every time.

function checkServers() {
    console.log("Inside checkServer");
    console.log("Before apis to test");

    // apisToTest has length of 2
    apisToTest.forEach(async (apiToTest) => {
        console.log("Api to test");
        let res = await axios(apiToTest)
        console.log("x"); // This gets skipped
        console.log(res); // This gets skipped
    })
    console.log("After api to test")
}

exports.lambdaHandler = async (event, context) => {
    console.log("Inside lambda handler");
    checkServers();
    console.log("After lambda handler");
};

// Used to test app using node command
checkServers()

This yields the following output:

INFO    Inside lambda handler     
INFO    Inside checkServer        
INFO    Before apis to test       
INFO    Api to test
INFO    Api to test
INFO    After api to test
INFO    After lambda handler
mkrieger1
  • 19,194
  • 5
  • 54
  • 65
Sal
  • 1,471
  • 2
  • 15
  • 36
  • https://stackoverflow.com/a/37576787/4388775 use rather a `for ... of` loop instead. – L. Meyer Nov 01 '20 at 08:44
  • Right, `forEach` doesn’t await async functions, and nothing else in your code is either. – deceze Nov 01 '20 at 08:53
  • Thanks for the reply, I gave this a shot, I changed the `forEach` to a `for ... of` and made `checkServer` `async`, but it didn't resolve the issue unfortunately, the values after the `await` line still seem to get skipped. Even if `forEach` doesn't await async functions, that shouldn't prevent "x" or `res` (even if it's `undefined`) from printing, right? – Sal Nov 01 '20 at 08:54
  • 1
    How about try-catching to see if exceptions are just not thrown there? – Wiktor Zychla Nov 01 '20 at 09:42
  • `let res = await axios(apiToTest) ` probably throws an exception. As you are not catching it, code execution is aborted . – derpirscher Nov 01 '20 at 10:45
  • and why do you think aws lamda won't await a `.then()`? You just have to return a promise from your handler. https://docs.aws.amazon.com/lambda/latest/dg/nodejs-handler.html – derpirscher Nov 01 '20 at 10:59
  • You were right, `axios` was throwing an error, and I was hoping there was a simpler solution that I hadn't considered than working with `Promise`, thanks for your help! – Sal Nov 03 '20 at 05:19
  • Async/await does not work with `forEach` (they also don't work with `map` or `filter` or `reduce` or `find` etc). Change it to `for (let i=0; i< apisToTest.length; i++) ...` – slebetman Nov 03 '20 at 05:42
  • Note that the `console.log("After API test")` happens before the API test. Like I said, `forEach` does not wait for the async function – slebetman Nov 03 '20 at 05:44

3 Answers3

1

Thanks for all of your replies, unfortunately those weren't the ideal solutions for my use case, though they were very helpful in me coming up with the solution.

async function checkServers() {
    let emailBody = "";
    let callResult = "";
    let completedCalls = 0;
    let promises = [];
    for (const apiToTest of apisToTest) {
        await axios(apiToTest).then((res) => {
            // Do something
        }).catch((r) => {
            // Handle error
        })
    }
}

exports.lambdaHandler = async (event, context) => {
    context.callbackWaitsForEmptyEventLoop = true;
    await checkServers();
};

To summarize, I replaced the forEach call to a for...of call, changed the checkServers to async, and combined await with .then() and .catch to handle the Promise result. I was unaware that both can be used to do so in tandem. Hope this helps anyone that had an issue similar to the one I had.

Sal
  • 1,471
  • 2
  • 15
  • 36
  • One small suggestion, its a common problem but there looks to be a mixture of the async/await style and the .then() style. Consider: ```for (const apiToTest of apisToTest) { axios(apiToTest).then((res) => { // Do something }).catch((r) => { // Handle error }) }``` or ```try { for (const apiToTest of apisToTest) { const result = await axios(apiToTest); } } catch(err) { // Handle error }``` – React Dev Jun 23 '21 at 19:08
0

you got two options:

  1. set callbackWaitsForEmptyEventLoop to true - and then AWS will wait until the eventloop is empty
  2. await your promises. Note that you didn't await your promises on checkServers. Consider using await Promise.all([...]) to wait for all your promises to finish.

Note: If you will run the lambda again, the events from the previous invocations may "leak" to the next invocation. You can learn about it here: https://lumigo.io/blog/node-js-lambda-execution-leaks-a-practical-guide/

Disclosure: I work in the company that published this blog post

saart
  • 344
  • 1
  • 5
  • Thanks for the response. I had attempted using `await Promise.all()`, however the challenge I ran into was that if one API call failed, then the entire result would throw an error. I want to handle individual errors, as I am expecting those to come up, and I wasn't able to find a way to do that with `Promise.all()` – Sal Nov 03 '20 at 05:25
  • There are many very elegant ways to handle separate errors. Check out this thread: https://stackoverflow.com/questions/30362733/handling-errors-in-promise-all – saart Nov 04 '20 at 06:50
0

Similar to what saart is saying you could try the below code and see if it works:

async function checkServers() {

  const promises = apisToTest.map((apiToTest) => axios(apiToTest))

  const resolvedPromises = await Promise.all(promises);

  return resolvedPromises;
}

exports.lambdaHandler = async (event, context) => {
    try {
      const result = await checkServers();
      console.log(result);
    } catch (error) {
      console.log(error)
    }
    
};

// Used to test app using node command
checkServers();

React Dev
  • 420
  • 2
  • 6
  • 16
  • Thanks for your reply. The challenge with `Promise.all()` was that it would error if one of the `Promises` errored. I'm expecting API calls to fail from time to time and don't want the entire `Promise` to fail, rather keep track of what passed and what failed and keep track of those. – Sal Nov 03 '20 at 05:27
  • Hi @Sal, no problem :) I have updated the code to not make the lambda handler fail. I did this by wrapping it in a try ... catch statement, so when an error is thrown it will just be logged without failing. You can learn more about try catch statements and how they work with promises here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function – React Dev Nov 03 '20 at 21:21