7

Now day we can use the AWS StepFunctions if we want to make an lambda function to call another one. But for now I need do support the code in production that was written before the StepFunctions time. For that reason I need to understand how it works. I was trying to create a very simple lambda calling another lambda function trough AWS-SDk.

I have the follow serverless.yml

service: lambdaCallLambda

provider:
  name: aws
  runtime: nodejs6.10

functions:
  hello:
    handler: handler.hello
  funcOne:
    handler: handler.funcOne  
  funcTwo:
    handler: handler.funcTwo

#Must install aws-sdk.  #npm install --save aws-sdk

And this is the handler.js:

'use strict';


//https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Lambda.html
var Lambda = require('aws-sdk/clients/lambda');


module.exports.hello = (event, context, callback) => {
  const response = {
    statusCode: 200,
    body: JSON.stringify({
      message: 'hello',
      input: event,
    }),
  };

  callback(null, response);
};


module.exports.funcOne = (event, context, callback) => { 
  var text='';
  var i = 0;
  for (i = 0; i < 5; i++) {
    text += "The number is " + i + "\n";
  }
  console.log(text);


  //https://docs.aws.amazon.com/general/latest/gr/rande.html
  const lambda = new Lambda({
    region: 'us-east-1'
  });
  console.log('control 3');



  /*

    https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Lambda.html#constructor-property
    To invoke a Lambda function
    This operation invokes a Lambda function

    https://docs.aws.amazon.com/lambda/latest/dg/API_Invoke.html
    Payload - JSON that you want to provide to your Lambda function as input.

  */
  var params = {
    ClientContext: "lambdaCallLambda", 
    FunctionName: "lambdaCallLambda-dev-funcOne", 
    InvocationType: "Event", 
    LogType: "Tail", 
    Payload: '{"jsonKey2":123}', 
    Qualifier: "1"
  };
  lambda.invoke(params, function(err, data) {
    if (err){
      console.log('control error\n');
      console.log(err, err.stack); // an error occurred
    } 
    else{
      console.log('control OK\n');
      console.log(data);           // successful response
    }     
    /*
    data = {
      FunctionError: "", 
      LogResult: "", 
      Payload: <Binary String>, 
      StatusCode: 123
    }
    */
  });  
};


module.exports.funcTwo = async (event, context) => { 
  return 2;
  //return '{"funcTwo":20000}';
  //console.log("funcTwo = " + event);
};  

After deploy sls deploy and call funcOne I get this 2 outputs:

LOCAL: sls invoke local --function funcOne

Serverless: INVOKING INVOKE
The number is 0
The number is 1
The number is 2
The number is 3
The number is 4

control 3
control OK

{ StatusCode: 202, Payload: '' }

Invoking remotely in AWS: sls invoke --function funcOne

{
    "errorMessage": "Unexpected token (",
    "errorType": "SyntaxError",
    "stackTrace": [
        "                               ^",
        "SyntaxError: Unexpected token (",
        "createScript (vm.js:56:10)",
        "Object.runInThisContext (vm.js:97:10)",
        "Module._compile (module.js:542:28)",
        "Object.Module._extensions..js (module.js:579:10)",
        "Module.load (module.js:487:32)",
        "tryModuleLoad (module.js:446:12)",
        "Function.Module._load (module.js:438:3)",
        "Module.require (module.js:497:17)",
        "require (internal/module.js:20:19)"
    ]
}

  Error --------------------------------------------------

  Invoked function failed

     For debugging logs, run again after setting the "SLS_DEBUG=*" environment variable.

  Get Support --------------------------------------------
     Docs:          docs.serverless.com
     Bugs:          github.com/serverless/serverless/issues
     Issues:        forum.serverless.com

  Your Environment Information -----------------------------
     OS:                     linux
     Node Version:           8.11.3
     Serverless Version:     1.29.2

Does someone knows hat is happening here? Specially for the first scenario where I dont have any error. This is what I get from the documentation

Parameters:

err (Error) — the error object returned from the request. Set to null if the request is successful.
data (Object) — the de-serialized data returned from the request. Set to null if a request error occurs. The data object has the following properties:
Status — (Integer)
It will be 202 upon success.

Update

After Eduardo Díaz suggestion - I have changed lambda.invoke to:

lambda.invoke({
    FunctionName: 'lambdaCallLambda-dev-funcOne',
    Payload: JSON.stringify(event, null, 2)
  }, function(error, data) {
    if (error) {
      console.log('control ErrorFoncOne\n');
      context.done('error', error);
    }
    if(data.Payload){
     console.log('control SuccessFoncOne\n'); 
     context.succeed(data)
    }
  });

And this what I get for Local and Remote:

{
    "errorMessage": "Unexpected token (",
    "errorType": "SyntaxError",
    "stackTrace": [
        "Module.load (module.js:487:32)",
        "tryModuleLoad (module.js:446:12)",
        "Function.Module._load (module.js:438:3)",
        "Module.require (module.js:497:17)",
        "require (internal/module.js:20:19)"
    ]
}

It is a SyntaxError. There is a "(" somewhere. I have found another developer with the same error here.

Note:

No error logs in CloudWatch

IgorAlves
  • 5,086
  • 10
  • 52
  • 83

4 Answers4

1

just a change of one line would get you the respose do change in params

    'use strict';


   //https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Lambda.html
    var Lambda = require('aws-sdk/clients/lambda');


    module.exports.hello = (event, context, callback) => {
     const response = {
     statusCode: 200,
     body: JSON.stringify({
     message: 'hello',
     input: event,
      }),
     };

    callback(null, response);
    };


   module.exports.funcOne = (event, context, callback) => { 
    var text='';
    var i = 0;
    for (i = 0; i < 5; i++) {
    text += "The number is " + i + "\n";
     }
     console.log(text);


      //https://docs.aws.amazon.com/general/latest/gr/rande.html
     const lambda = new Lambda({
     region: 'us-east-1'
      });
      console.log('control 3');

             
         var params = {
          ClientContext: "lambdaCallLambda", 
          FunctionName: "lambdaCallLambda-dev-funcOne", 
          InvocationType: "RequestResponse", /* changed this line from 
                                 "Event" to "RequestResponse"*/
          LogType: "Tail", 
          Payload: '{"jsonKey2":123}', 
          Qualifier: "1"
           };
       lambda.invoke(params, function(err, data) {
        if (err){
        console.log('control error\n');
        console.log(err, err.stack); // an error occurred
        } 
      else{
        console.log('control OK\n');
        console.log(data);           // successful response
        }     
       /*
       data = {
       FunctionError: "", 
       LogResult: "", 
       Payload: <Binary String>, 
       StatusCode: 123
          }
           */
       });  
      };


    module.exports.funcTwo = async (event, context) => { 
     return 2;
    //return '{"funcTwo":20000}';
    //console.log("funcTwo = " + event);
     };  
0

Try to send the event in the payload:

lambda.invoke({
  FunctionName: 'name_lambda_function',
  Payload: JSON.stringify(event, null, 2)
}, function(error, data) {
  if (error) {
    context.done('error', error);
  }
  if(data.Payload){
   context.succeed(data.Payload)
  }
});
  • Hey Eduardo, I just ran your changes. I am getting in local the same error I was getting remote. I think now is better. At least I can see the error in both sides. The complaint is about a "(" somewhere. I have reviewed the code and I cat find where is it. – IgorAlves Aug 05 '18 at 12:50
0

I strongly suspect this problem is rooted in your handler signature for funcTwo:

module.exports.funcTwo = async (event, context) => { 

NodeJS 6.10 does not support async/await. Node always complains about the token after the async token, for whatever reason. If you don't use a fat arrow function:

module.exports.funcTwo = async function(event, context) {

Node will complain: Unexpected token function.

Options

  • Deploy the function to NodeJS 8.10 instead.
  • Get rid of the async keyword in the handler signature.
  • Use a build tool (like serverless-webpack) to transpile the function down to ES6 (or lower).

Note: If you stick with the 6.10 runtime, I think you'll want to do something like context.succeed(2); or callback(null, 2);, rather than return 2;. Simply using a return statement does seem to work on 8.10.

Mike Patrick
  • 10,699
  • 1
  • 32
  • 54
0

I have found the issue. We need JSON.stringfy in the playload in lamdda.invoke method. No extra parameters are needed. Only the JSON as per the documentation.

lambda.invoke({
      FunctionName: 'lambdaCallLambda-dev-funcTwo',
      Payload: JSON.stringify({"jsonKey2":i})
...

From the AWS documentation for playload we have:

Payload
JSON that you want to provide to your Lambda function as input.

Note:

the async in front of the functions

lambda.invoke({
      FunctionName: 'lambdaCallLambda-dev-funcTwo',
      Payload: JSON.stringify({"jsonKey2":i})
    }, async function(error, data) { ...

and

module.exports.funcTwo = async(event, context, callback) => { ...

Gives me this output:

Loop nb=1

Loop nb=2

Loop nb=3

Loop nb=4

Loop nb=5

{"message":"hello from funcTwo","event":{"jsonKey2":3}}
{"message":"hello from funcTwo","event":{"jsonKey2":2}}
{"message":"hello from funcTwo","event":{"jsonKey2":1}}
{"message":"hello from funcTwo","event":{"jsonKey2":5}}
{"message":"hello from funcTwo","event":{"jsonKey2":4}}

While the absence of async gives me:

Loop nb=1

Loop nb=2

Loop nb=3

Loop nb=4

Loop nb=5

{"message":"hello from funcTwo","event":{"jsonKey2":1}}
{"message":"hello from funcTwo","event":{"jsonKey2":2}}
{"message":"hello from funcTwo","event":{"jsonKey2":3}}
{"message":"hello from funcTwo","event":{"jsonKey2":4}}
{"message":"hello from funcTwo","event":{"jsonKey2":5}}

I'm going to share the handle.js code just in case someone else needs it:

'use strict';


//https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Lambda.html
var Lambda = require('aws-sdk/clients/lambda');


module.exports.hello = (event, context, callback) => {
  const response = {
    statusCode: 200,
    body: JSON.stringify({
      message: 'hello',
      input: event,
    }),
  };

  callback(null, response);
};

/*

    https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Lambda.html#constructor-property
    To invoke a Lambda function
    This operation invokes a Lambda function

    https://docs.aws.amazon.com/lambda/latest/dg/API_Invoke.html
    Payload - JSON that you want to provide to your Lambda function as input.

    Serverless Framework: Lambdas Invoking Lambdas
    https://lorenstewart.me/2017/10/02/serverless-framework-lambdas-invoking-lambdas/

    How to escape async/await hell
    https://medium.freecodecamp.org/avoiding-the-async-await-hell-c77a0fb71c4c

    Iterating a Loop Using Lambda
    https://docs.aws.amazon.com/step-functions/latest/dg/tutorial-create-iterate-pattern-section.html


    AWS Lambda “Process exited before completing request”
    https://stackoverflow.com/questions/31627950/aws-lambda-process-exited-before-completing-request


    Class: AWS.Lambda
    https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Lambda.html#constructor-property
    Invoke
    https://docs.aws.amazon.com/lambda/latest/dg/API_Invoke.html#API_Invoke_RequestSyntax


    Programming Model(Node.js)
    https://docs.aws.amazon.com/lambda/latest/dg/programming-model.html


    AWS Lambda Examples
    https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/lambda-examples.html

  */



//The event is a file inserted in S3. This function funcOne reads the file and loop trough all quantities of products.
//Invoking a lamda function funcTwo for each process in the loop.
module.exports.funcOne = (event, context, callback) => { 

  //https://docs.aws.amazon.com/general/latest/gr/rande.html
  const lambda = new Lambda({
    region: 'us-east-1'
  });

  //Loop
  //nbProducts = loop trough the products JSON list in S3    
  var nbProducts=5;  
  for (let i = 1; i <= nbProducts; i++) {
    console.log('Loop nb='+i+'\n');

  lambda.invoke({
      FunctionName: 'lambdaCallLambda-dev-funcTwo',
      Payload: JSON.stringify({"jsonKey2":i})
    }, async function(error, data) {
      if (error) {
        //console.log('control ErrorFoncOne\n');
        context.done('error', error);
      }
      if(data.Payload){
       //console.log('control SuccessFoncOne\n'); 
       console.log(data.Payload);
       //context.succeed(data)
      }
    });
  }  
};


module.exports.funcTwo = async(event, context, callback) => {
   callback(null, { message: 'hello from funcTwo', event });
};
IgorAlves
  • 5,086
  • 10
  • 52
  • 83