How do I promisify the AWS JavaScript SDK? - node.js

I want to use the aws-sdk in JavaScript using promises.
Instead of the default callback style:
dynamodb.getItem(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
});
I instead want to use a promise style:
dynamoDb.putItemAsync(params).then(function(data) {
console.log(data); // successful response
}).catch(function(error) {
console.log(err, err.stack); // an error occurred
});

The 2.3.0 release of the AWS JavaScript SDK added support for promises: http://aws.amazon.com/releasenotes/8589740860839559

I believe calls can now be appended with .promise() to promisify the given method.
You can see it start being introduced in 2.6.12 https://github.com/aws/aws-sdk-js/blob/master/CHANGELOG.md#2612
You can see an example of it's use in AWS' blog https://aws.amazon.com/blogs/compute/node-js-8-10-runtime-now-available-in-aws-lambda/
let AWS = require('aws-sdk');
let lambda = new AWS.Lambda();
exports.handler = async (event) => {
return await lambda.getAccountSettings().promise() ;
};

You can use a promise library that does promisification, e.g. Bluebird.
Here is an example of how to promisify DynamoDB.
var Promise = require("bluebird");
var AWS = require('aws-sdk');
var dynamoDbConfig = {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
region: process.env.AWS_REGION
};
var dynamoDb = new AWS.DynamoDB(dynamoDbConfig);
Promise.promisifyAll(Object.getPrototypeOf(dynamoDb));
Not you can add Async to any method to get the promisified version.

Way overdue, but there is a aws-sdk-promise npm module that simplifies this.
This just adds a promise() function which can be used like this:
ddb.getItem(params).promise().then(function(req) {
var x = req.data.Item.someField;
});
EDIT: It's been a few years since I wrote this answer, but since it seems to be getting up-votes lately, I thought I'd update it: aws-sdk-promise is deprecated, and newer (as in, the last couple of years) versions of aws-sdk includes built-in promise support. The promise implementation to use can be configured through config.setPromisesDependency().
For example, to have aws-sdk return Q promises, the following configuration can be used:
const AWS = require('aws-sdk')
const Q = require('q')
AWS.config.setPromisesDependency(Q.Promise)
The promise() function will then return Q promises directly (when using aws-sdk-promise, you had to wrap each returned promise manually, e.g. with Q(...) to get Q promises).

With async/await I found the following approach to be pretty clean and fixed that same issue for me for DynamoDB. This works with ElastiCache Redis as well. Doesn't require anything that doesn't come with the default lambda image.
const {promisify} = require('util');
const AWS = require("aws-sdk");
const dynamoDB = new AWS.DynamoDB.DocumentClient();
const dynamoDBGetAsync = promisify(dynamoDB.get).bind(dynamoDB);
exports.handler = async (event) => {
let userId="123";
let params = {
TableName: "mytable",
Key:{
"PK": "user-"+userId,
"SK": "user-perms-"+userId
}
};
console.log("Getting user permissions from DynamoDB for " + userId + " with parms=" + JSON.stringify(params));
let result= await dynamoDBGetAsync(params);
console.log("Got value: " + JSON.stringify(result));
}

Folks,
I've not been able to use the Promise.promisifyAll(Object.getPrototypeOf(dynamoDb));
However, the following worked for me:
this.DYNAMO = Promise.promisifyAll(new AWS.DynamoDB());
...
return this.DYNAMO.listTablesAsync().then(function (tables) {
return tables;
});
or
var AWS = require('aws-sdk');
var S3 = Promise.promisifyAll(new AWS.S3());
return S3.putObjectAsync(params);

CascadeEnergy/aws-promised
We have an always in progress npm module aws-promised which does the bluebird promisify of each client of the aws-sdk. I'm not sure it's preferable to using the aws-sdk-promise module mentioned above, but here it is.
We need contributions, we've only taken the time to promisify the clients we actually use, but there are many more to do, so please do it!

This solution works best for me:
// Create a promise object
var putObjectPromise = s3.putObject({Bucket: 'bucket', Key: 'key'}).promise();
// If successful, do this:
putObjectPromise.then(function(data) {
console.log('PutObject succeeded'); })
// If the promise failed, catch the error:
.catch(function(err) {
console.log(err); });

Related

Get secrets in AWS lambda node.js

Can anyone provide a simple, complete node.js lambda function where I can get a secret from secrets manager and use it? I am struggling with the async/await process. I have already tried several suggestions from other posts, but all of them, at the end, can't really use the secret in the main function.
For example, I have a main function and call a second function to retrieve the secret:
xxx = retrieve_secret('mysecret');
Then, in the retrieve_secret function I am able to retrieve the secret, I can print it using console.log, but when I try to use it in the main function, it says "Promise ".
Please, help.
Thanks in advance!
So, after a few days working on it, I was finally able to solve it :)
Here is the code that worked for me:
exports.handler = async (event, context, callback) => {
// Get Secret
var AWS = require('aws-sdk');
var MyPromise = new AWS.SecretsManager();
var Vsecret = await MyPromise.getSecretValue({
SecretId: 'enter-the-secret-id-here'
}).promise();
var MyOpenSecret = JSON.parse(Vsecret.SecretString);
// From here, we can use the secret:
var Vhost = MyOpenSecret.host;
var Vuser = MyOpenSecret.username;
var Vpassword = MyOpenSecret.password;
var Vdatabase = .....
Looking at your question seems you are not able to read response from retrieve_secret('mysecret') method as you have mentioned it return promise, you can read it by using .then() after promise. Try doing this -
xxx.then(res => {
console.log(res)
})
Or here is the code to call get your secret details:
import AWS from "aws-sdk";
getSecretValue(secretName: string): Promise<string> {
const client = new AWS.SecretsManager({
region: '',
accessKeyId: '',
secretAccessKey: '',
});
const secretId = "secretName";
return new Promise((resolve, reject) =>
client.getSecretValue({ SecretId: secretId }, (err, data) => {
if (err) {
reject(err);
} else {
resolve(data.SecretString);
}
})
);
}

Create AWS Lambda function to get Users from IAM

I want to get user details from AWS IAM so I have created a lambda function but there is an error with response code 502. My code is as below.
var AWS = require('aws-sdk');
var iam = new AWS.IAM();
AWS.config.loadFromPath('./config.json');
let getUsers = async (event, callback) => {
var params = {
UserName: "5dc6f49d50498e2907f8ee69"
};
iam.getUser(params, (err, data) => {
if (err) {
callback(err)
} else {
console.log(data)
callback(data)
}
})
};
Since your function already is async you don't need to use the old, outdated callback approach.
The AWS SDK methods provide a .promise() method which you can append to every AWS asynchronous call and, with async, you can simply await on a Promise.
var AWS = require('aws-sdk');
var iam = new AWS.IAM();
AWS.config.loadFromPath('./config.json');
let getUsers = async event => {
var params = {
UserName: "5dc6f49d50498e2907f8ee69"
};
const user = await iam.getUser(params).promise()
console.log(user)
};
Hopefully you have a handler that invokes this code, otherwise this is not going to work.
If you want to export getUsers as your handler function, make sure export it through module.exports first. I.e: module.exports.getUsers = async event.... Double check that your Lambda handler is properly configured on the function itself, where index.getUsers would stand for index being the filename (index.js) and getUsers your exported function.

Invalid JSON when calling Node 8.10 Lambda

I am using lambda with cognito to write to dynamoDB after a successful login.
Node 8.10 has a different layout with the promise and asycn/await. the callback(null, event) return is not working for my. Anyone now how to solve the problem of Invalid lambda function output : Invalid JSON with node 8.10.
// Load the AWS SDK for Node.js
var AWS = require('aws-sdk');
// Set the region
//AWS.config.update({region: 'REGION'});
// Create DynamoDB document client
var docClient = new AWS.DynamoDB.DocumentClient({apiVersion: '2012-08-10'});
exports.myHandler = async (event, context, callback) => {
// TODO implement
console.log ("Authentication successful");
console.log ("Trigger function =", event.triggerSource);
console.log ("User pool = ", event.userPoolId);
console.log ("App client ID = ", event.callerContext.clientId);
console.log ("User ID = ", event.userName);
const params = {
TableName: 'xxxx',
Item: {
'userId': event.userName,
'systemUpdateDate': new Date().toJSON()
}
};
let putItem = new Promise((res, rej) => {
docClient.put(params, function(err, data) {
if (err) {
console.log("Error", err);
} else {
console.log("Success", data);
}
});
});
const result = await putItem;
console.log(result);
// Return to Amazon Cognito
callback(null, event);
};
thanks
With the suggested Node 8 approach of async/await you should use the following approach to structuring your function:
async function handler(event) {
const response = doSomethingAndReturnAJavascriptObject();
return response;
}
You're getting that error because whatever you're returning isn't something that can be parsed as a JSON object.
Without seeing your code it's hard to debug further. I expect that you might accidentally not be awaiting a .promise() version of a dynamo/cognito API call, which is causing you to return a Promise instead of a result.
n.b. you can still use the 'old' callback() method with Node 8 if you find it easier.

How to use Async and Await with AWS SDK Javascript

I am working with the AWS SDK using the KMS libary. I would like to use async and await instead of callbacks.
import AWS, { KMS } from "aws-sdk";
this.kms = new AWS.KMS();
const key = await this.kms.generateDataKey();
However this does not work, when wrapped in an async function.
How can i use async and await here?
If you are using aws-sdk with version > 2.x, you can tranform a aws.Request to a promise with chain .promise() function.
For your case:
try {
let key = await kms.generateDataKey().promise();
} catch (e) {
console.log(e);
}
the key is a KMS.Types.GenerateDataKeyResponse - the second param of callback(in callback style).
The e is a AWSError - The first param of callback func
note: await expression only allowed within an async function
await requires a Promise. generateDataKey() returns a AWS.Request, not a Promise. AWS.Request are EventEmitters (more or less) but have a promise method that you can use.
import AWS, {
KMS
} from "aws-sdk";
(async function() {
const kms = new AWS.KMS();
const keyReq = kms.generateDataKey()
const key = await keyReq.promise();
// Or just:
// const key = await kms.generateDataKey().promise()
}());
As of 2021 I'd suggest to use AWS SDK for JavaScript v3. It's a rewrite of v2 with some great new features
sample code:
const { KMSClient, GenerateDataKeyCommand } = require('#aws-sdk/client-kms');
const generateDataKey = async () => {
const client = new KMSClient({ region: 'REGION' });
const command = new GenerateDataKeyCommand({ KeyId: 'KeyId' });
const response = await client.send(command);
return response;
};
AWS SDK for JavaScript v3 new features
modular architecture with a separate package for each service
First-class TypeScript support
New middleware stack

Mocking using aws-sdk-mock's promise support with DocumentClient

I'm trying to write a unit test using aws-sdk-mock's promise support. I'm using DocumentClient.
My code looks like this:
const docClient = new AWS.DynamoDB.DocumentClient();
const getItemPromise = docClient.get(params).promise();
return getItemPromise.then((data) => {
console.log('Success');
return data;
}).catch((err) => {
console.log(err);
});
My mock and unit test looks like this:
const AWS = require('aws-sdk-mock');
AWS.Promise = Promise.Promise;
AWS.mock('DynamoDB.DocumentClient', 'get', function (params, callback)
{
callback(null, { Item: { Key: 'test value } });
});
dynamoStore.getItems('tableName', 'idName', 'id').then((actualResponse) => {
// assertions
done();
});
Runnning my unit test, does not return my test value, it actually bypasses my mock, and calls calls dynamoDb directly. What am I doing wrong? How can I get my mock set up properly?
It's unclear from your code but aws-sdk-mock has this note
NB: The AWS Service needs to be initialised inside the function being tested in order for the SDK method to be mocked
so the following will not mock correctly
var AWS = require('aws-sdk');
var sns = AWS.SNS();
var dynamoDb = AWS.DynamoDB();
exports.handler = function(event, context) {
// do something with the services e.g. sns.publish
}
but this will
var AWS = require('aws-sdk');
exports.handler = function(event, context) {
var sns = AWS.SNS();
var dynamoDb = AWS.DynamoDB();
// do something with the services e.g. sns.publish
}
see more here https://github.com/dwyl/aws-sdk-mock#how-usage
It might be too late for an answer, but I had the same problem and I stumbled upon this question. After a few tries I found a solution that doesn't involve aws-sdk-mock but only plain Sinon, and I hope that sharing it would help someone else. Note that the DynamoDB client is create outside the lambda.
The lambda itself looks like this:
const dynamoDB = new DynamoDB.DocumentClient();
exports.get = async event => {
const params = {
TableName: 'Tasks',
Key: {
id: event.pathParameters.id
}
};
const result = await dynamoDB.get(params).promise();
if (result.Item) {
return success(result.Item);
} else {
return failure({ error: 'Task not found.' });
}
};
And the test for this lambda is:
const sandbox = sinon.createSandbox();
describe('Task', () => {
beforeAll(() => {
const result = { Item: { id: '1', name: 'Go to gym'}};
sandbox.stub(DynamoDB.DocumentClient.prototype, 'get').returns({promise: () => result});
});
afterAll(() => {
sandbox.restore();
});
it('gets a task from the DB', async () => {
// Act
const response = await task.get(getStub);
// Assert
expect(response.statusCode).toEqual(200);
expect(response.body).toMatchSnapshot();
});
});
I like to use Sinon's sandbox to be able to stub a whole lot of different DynamoDB methods and clean up everything in a single restore().
sinon and proxyquire can be used to mock the dynamodb client.
It supports both callback based and async/await based calls.
Refer this link for full details
https://yottabrain.org/nodejs/nodejs-unit-test-dynamodb/
Somewhat related to the question, expanding wyu's solution - i too faced similar issue - for me, below didn't work with aws-sdk-mock
const AWS = require('aws-sdk');
AWS.config.update({region: 'us-east-1'});
let call = function (action, params) {
const dynamoDb = new AWS.DynamoDB.DocumentClient();
return dynamoDb[action](params).promise();
};
where as this worked
let call = function (action, params) {
const AWS = require('aws-sdk');
AWS.config.update({region: 'us-east-1'});
const dynamoDb = new AWS.DynamoDB.DocumentClient();
return dynamoDb[action](params).promise();
};
I had exactly the same problem of mock failing but resolved the issue after following the suggestion by a user who above by moving the following line within the function rather than defining outside:
let sns = new AWS.SNS(.....)

Resources