Querying DynamoDB with Lambda does nothing

15.6k views Asked by At

I have the following code for a Lambda function:

console.log('Loading function');
var aws = require('aws-sdk');
var ddb = new aws.DynamoDB();

function getUser(userid) {
    var q = ddb.getItem({
        TableName: "Users",
        Key: {
            userID: { S: userid } }
        }, function(err, data) {
            if (err) {
                console.log(err);
                return err;
            }
            else {
                console.log(data);
            }
    });
    console.log(q);
}


exports.handler = function(event, context) {
    console.log('Received event');
    getUser('user1');
    console.log("called DynamoDB");
    context.succeed();
};

I have a [Users] table that is defined as such:

{
    "cognitoID": { "S": "token" },
    "email": { "S": "[email protected]" },
    "password": { "S": "somepassword" },
    "tos_aggreement": { "BOOL": true },
    "userID": { "S": "user1" }
}

When I call the function (from the AWS Console or the CLI) I can see the messages in the logs but the callback for the getItem() is never called.

I tried with doing getItem(params) with no callback, then defined the callbacks for complete, success and failure but when I do the send(), even the complete callback isn't called either.

I know that the calls are asynchronous and I thought that maybe, the lambda function was finishing before the query was done and therefore the callback would not be called, but, I added a simple stupid loop at the end of the function and the call timed out after 3 seconds, without the callbacks being called at all.

I tried with different functions batchGetItem, getItem, listTables and scan. Result is the same, no error but the callback function is never called.

I'm betting that if I query dynamoDB without using Lambda it will get me the results so I'm really wondering why nothing is happening here.

I create a role for the function and I created a policy that would allow access to the functionalities in dynamoDB that I need but to no avail.

The policy looks like this:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "lambda:InvokeFunction"
            ],
            "Resource": "arn:aws:lambda:*:*:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:GetItem",
                "dynamodb:BatchGetItem",
                "dynamodb:Scan",
                "dynamodb:PutItem",
                "dynamodb:Query",
                "dynamodb:GetRecords",
                "dynamodb:ListTables"
            ],
            "Resource": "arn:aws:dynamodb:*:*:*"
        },
        {
            "Action": [
                "logs:*"
            ],
            "Effect": "Allow",
            "Resource": "*"
        }
    ]
}

I ran the policy in the simulator and it worked as I thought it would. Suggestions?

6

There are 6 answers

1
E.T On BEST ANSWER

So, it turns out that the code is correct. The problem is that the dynamodb API uses all those callbacks and basically the function ends BEFORE the data has been retrieved.

The quickest fix is to remove the context.succeed() call and the data will be retrieved. Of course using the async module would help and if you don't want to use that, just add a counter or a boolean to your callback and then wait until the value has changed, indicating that the callback has been called (which kind of sucks if you think of it)

1
Justin Chapweske On

My problem is that my lambda was running in a VPC in order to connect to ElastiCache. This causes any queries to public Internet resources such as DynamoDB and API Gateway to hang indefinitely. I had to set up a NAT Gateway within my VPC in order to access DynamoDB.

1
Nicolai Lissau On

My problem was that the function I was trying to call accepted a callback.

Node.js therefore just continued the execution of the Lambda function and once it reaches the end it tells Lambda to shut down because it's done.

Here's an example of how you can solve it using Promises:

'use strict';

exports.handler = async (event, context) => {

  // WRAP THE FUNCTION WHICH USES A CALLBACK IN A PROMISE AND RESOLVE IT, WHEN
  // THE CALLBACK FINISHES

  return await new Promise( (resolve, reject) => {

    // FUNCTION THAT USES A CALLBACK
    functionThatUsesACallback(params, (error, data) => {
      if(error) reject(error)
      if(data) resolve(data)
    });

  });

};
0
yks On

To avoid callback hell, use Promises. There are some pretty good tutorials on youtube by guy called funfunfunction.

2
Derek Maxson On

I had some similar issues and did not find many helpful resources. Here's what I ended up doing. Probably someone smarter can tell us if this is bestest.

function getHighScores(callback) {
    var params = {
        TableName : 'sessions',
        ScanFilter: {"score":{"AttributeValueList":[{"N":"0"}], "ComparisonOperator":"GT"}},
    };
    var dynamo = new AWS.DynamoDB();
    dynamo.scan(params, function(err, data) {
        if (err) {
            console.log (err)
            callback(err);
        } else {
            callback(data.Items);
            console.log(data.Items);
        }
    });
} 



getHighScores(function (data) {
    console.log(data);
    context.succeed(data);
});

In summary, having the passback of callback through the main function to the smaller function, allows the application to continue until completing the Dynamo. Keep the context.succeed in the secondary function or continue other function there.

0
Neil Yue On

Now since node.js has introduced async/await, this can make it wait until the query call returns before the main function terminates:

let result = await ddb.getItem(params).promise();
return result;