Search code examples
aws-lambdaslack

Suggest approach for handling multiple lambda invocations


Main Problem: Lambda function is not exiting after successful external API call, subsequently triggering multiple external API calls.

I have a lambda function that is listening for slack events when an event takes place the lambda function is invoked. I filter these events using some condition checks, and when the targetted event takes place I make an external API call.

The above all works, however, the external API call is being triggered multiple times per one event (~4 times when it should be 1 if successful).

Idempotent - Often people associate this issue with idempotency, however, given its the external API call within a condition block that is being triggered additional times I do not believe this an issue.

  • I can confirm that the condition check is correctly filtering the appropriate events
  • I can confirm I am receiving a statusCode 200 before the subsequent (additional) external API calls
  • I have included context.done() and redundant callback() with no luck

Looking for some suggested approaches

  • Does anybody have a suggested approach to remedy this issue?
  • I have read that some people use DynamoDB to track execution requests but this does not sound like a great approach

I have attached the lambda function below:

const axios = require('axios');

const sendMessages = async(event, context, callback) => {
    // test the message for a match
    if (event.type === 'message' && event.bot_id !== undefined) {
        console.log("filter events for specific event");

        let URL = 'https://someurl.com/slack/events';
        let response = await axios.post(URL, {
            events: event,
        });
        console.log("response success :: ", response.status);
        if (response.status === 200) {
            console.log("condition met");
            context.done();
            callback(null, 'successful request');
        }
    }
    callback(null, 'successful request');
};

// Lambda handler
exports.server = (data, context, callback) => {
    let eventData = JSON.parse(data.body);
    switch (data.path) {
        case "/slack-events": sendMessages(eventData.event, context, callback); break;
        default: callback(null);
    }
};

Expected behavior

  • Make a single external API call upon invocation and condition check; exit after response.status === 200

Solution

  • The problem (you are not the first nor will you be the last one asking this type of question, AWS needs to fix the docs ASAP) is because you are mixing up async/await with context.done and callback() calls.

    If your functions already are async, then stick with await all the way through and forget about context and callback objects.

    See that sendMessages is async, therefore it returns a Promise, but you don't do anything with that Promise. You should await on it. I have changed your code accordingly and got rid of both context and callback objects, you don't need them.

    const axios = require('axios');
    
    const sendMessages = async (event) => {
      // test the message for a match
      if (event.type === 'message' && event.bot_id !== undefined) {
        console.log('filter events for specific event');
    
        let URL = 'https://someurl.com/slack/events';
        let response = await axios.post(URL, {
          events: event,
        });
        console.log('response success :: ', response.status);
        return response;
      }
      return Promise.resolve({});
    };
    
    // Lambda handler
    exports.server = async (event) => {
      let eventData = JSON.parse(event.body);
      switch (event.path) {
        case '/slack-events':
          await sendMessages(eventData.event);
          break;
      }
      return {
        message: 'Success',
      }
    };
    

    If this Lambda is invoked by API Gateway, then you need to return a 2xx status code (or 4xx and 5xx in case of errors) with a stringified body so it can terminate properly, like so:

    return {
            statusCode: 200,
            body: JSON.stringify({message: 'Success'})
          }