I am trying to properly structure my AWS lambda. I am attempting to exit the Lambda when I encounter an error. With the simple one function Lambda, I do this:
exports.handler = async (event, context) => {
const someError = new Error('Something is wrong');
throw someError;
};
It ends up in DeadQueue as expected (after several tries).
When I want to resolve it with success, I do this:
exports.handler = async (event, context) => {
// ... some code here
return {};
};
Now I want to structure my application using requires, so I have something like this:
//Main execution point
const validator = require('./Validate/Validator');
exports.handler = async (event, context) => {
let aaa = validator.validate(operationName);
console.log('I should not bere here')
};
And the validator itself:
exports.validate = async (schemaName, payload) => {
console.log('I am coming here')
try {
const Schema = require(`../ValidationSchemas/${schemaName}`).schema;
}
catch (e) {
const schemaError = new Error('Validation schema not found. Operation does not exist.');
throw schemaError;
//process.exit(0);
}
};
What happens in validator, if I throw an error, error is thrown, but my execution is continued in the main (caller) lambda function. I thought to stop it there using
process.exit(0)
It does work. Lambda is terminated. But it looks like a bad approach for some reason. Ideally I would do it from the main function, but I am thinking about the best approach.
You need to implement error handling in Lambda function.
const validator = require('./Validate/Validator');
exports.handler = (event) => {
try {
let aaa = validator.validate(operationName);
}
catch (error) {
return error;
}
};
Related
The goal is to enforce that some required parameter has been successfully passed, it's of the right type and otherwise, throw a correct and informative error.
If-Statements
This works, but requires too much boilerplate code.
export const joinSocialRequest = functions.https.onCall(async (data, context) => {
const eventId = data.eventId
if (eventId === undefined) {
throw new functions.https.HttpsError('unknown','Missing param eventId.')
}
...
}
Using preconditions-ts or preconditions NPM package
This is better, but all errors are thrown as "Internal", which doesn't let my client engineers know what's going on!
import { requiredStringFrom } from 'preconditions-ts';
export const acceptParticipantRequest = functions.https.onCall(async (data, context) => {
const eventId = requiredStringFrom(data, "eventId")
...
}
I can probably wrap the above in a try/catch and throw the correct error, but that means, I will need to write a lot of wrappers!
Is there a better way?
You could write your own validation assertion helper:
function validate(condition, message, code = 'invalid-argument') {
if (!condition) {
throw new functions.https.HttpsError(code, message);
}
}
export const joinSocialRequest = functions.https.onCall(async (data, context) => {
const eventId = data.eventId
validate(eventId !== undefined, 'Missing param eventId.');
}
I would like to have a configuration file with variables set with data I fetch from an API.
I think I must use async and await features to do so, otherwise my variable would stay undefined.
But I don't know how to integrate this and keep the node exports.myVariable = myData available within an async function ?
Below is the code I tried to write to do so (all in the same file) :
const fetchAPI = function(jsonQuery) {
return new Promise(function (resolve, reject) {
var reqOptions = {
headers: apiHeaders,
json:jsonQuery,
}
request.post(apiURL, function (error, res, body) {
if (!error && res.statusCode == 200) {
resolve(body);
} else {
reject(error);
}
});
});
}
var wallsData = {}
const fetchWalls = async function (){
var jsonQuery = [{ "recordType": "page","query": "pageTemplate = 1011"}]
let body = await utils.fetchAPI(jsonQuery)
let pageList = await body[0].dataHashes
for(i=0;i<pageList.length;i++){
var page = pageList[i]
wallsData[page.title.fr] = [page.difficultyList,page.wallType]
}
return wallsData
throw new Error("WOOPS")
}
try{
const wallsData = fetchWalls()
console.log(wallsData)
exports.wallsData = wallsData
}catch(err){
console.log(err)
}
The output of console.log(wallsData) shows Promise { <pending> }, therefore it is not resolved and the configuration file keep being executed without the data in wallsData...
What do I miss ?
Thanks,
Cheers
A promise is a special object that either succeeds with a result or fails with a rejection. The async-await-syntax is syntactic sugar to help to deal with promises.
If you define a function as aync it always will return a promise.
Even a function like that reads like
const foo = async() => {
return "hello";
}
returns a promise of a string, not only a string. And you need to wait until it's been resolved or rejected.
It's analogue to:
const foo = async() => {
return Promise.resolve("Hello");
}
or:
const foo = async() => {
return new Promise(resolve => resolve("Hello"));
}
Your fetchWalls similarly is a promise that will remain pending for a time. You'll have to make sure it either succeeds or fails by setting up the then or catch handlers in your outer scope:
fetchWalls()
.then(console.log)
.catch(console.error);
The outer scope is never async, so you cannot use await there. You can only use await inside other async functions.
I would also not use your try-catch for that outer scope promise handling. I think you are confusing the try-catch approach that is intended to be used within async functions, as there it helps to avoid nesting and reads like synchronous code:
E.g. you could do inside your fetchWalls defintion:
const fetchWalls = async function (){
var jsonQuery = [{ "recordType": "page","query": "pageTemplate = 1011"}]
try {
let body = await utils.fetchAPI(jsonQuery)
} catch(e) {
// e is the reason of the promise rejection if you want to decide what to do based on it. If you would not catch it, the rejection would chain through to the first error handler.
}
...
}
Can you change the statements like,
try{
const wallsData = fetchWalls();
wallsData.then((result) => {
console.log(result);
});
exports.wallsData = wallsData; // when importing in other file this returns as promise and we should use async/await to handle this.
}catch(err){
console.log(err)
}
I can't get adminUpdateUserAttributes for Cognito to work. The cli works and I can have the user add/changed them not desired but wanted to see it working.
I'm using the AmazonCognitoPowerUser an AWS managed policy on the lambda function and the lambda is triggering, is there something I'm missing this sounds and looks easy but it's just not working.
also is there a way to get the default Created date without making my own.
const AWS = require('aws-sdk');
const cognitoidentityserviceprovider = new AWS.CognitoIdentityServiceProvider();
exports.handler = async (event) => {
cognitoidentityserviceprovider.adminUpdateUserAttributes(
{
UserAttributes: [
{
Name: 'custom:Date_Created',
Value: new Date().toString()
}
....
],
UserPoolId: " the correctpool id",
Username: "dagTest"
},
function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data);
}
)};
// no errors and returns nothing as it says it should.
I guess it is because you are not waiting for the result and the lambda is terminating after adminUpdateUserAttributes() is called and dows not wait until it is returning.
I would suggest that you change to promise based calling and do a try/catch
exports.handler = async (event) => {
try{
// no callback here
const data = await cognitoidentityserviceprovider
.adminUpdateUserAttributes(attributes)
.promise()
console.log('success', data)
} catch(error) {
console.error('error', error)
}
)}
#thopaw The code is correct but didn't work for me as intended. I was still getting Auth Error in the front end even through the custom attributes were updated successfully in the Cognito AWS Console. I had to add context.done(null,event); in order to return the control to Cognito after the execution of lambda. So the updated one could be,
exports.handler = async (event, context) => {
try{
const data = await cognitoidentityserviceprovider
.adminUpdateUserAttributes(attributes)
.promise()
console.log('success', data)
} catch(error) {
console.error('error', error)
}
context.done(null,event);
)}
Trying to send several messages (from AWS SQS lambda, if that matters) but it's never waiting for the promises.
function getEndpoint(settings){
return new Promise(function(resolve, reject) {
// [...] more stuff here
}
Which is then called in a loop:
exports.handler = async (event) => {
var messages = [];
event.Records.forEach(function(messageId, body) {
//options object created from some stuff
messages.push(getEndpoint(options).then(function(response){
console.log("anything at all"); //NEVER LOGGED
}));
});
await Promise.all(messages);
};
But the await seems to be flat out skipped. I'm not sure how I'm getting Process exited before completing request with an explicit await. I have similar async await/promise setups in other scripts that work, but cannot spot what I've done wrong with this one.
You forgot to return something to lambda:
exports.handler = async (event) => {
var messages = [];
event.Records.forEach(function(messageId, body) {
//options object created from some stuff
messages.push(getEndpoint(options));
});
await Promise.all(messages);
return 'OK'
};
this should also work:
exports.handler = (event) => { // async is not mandatory here
var messages = [];
event.Records.forEach(function(messageId, body) {
//options object created from some stuff
messages.push(getEndpoint(options));
});
return Promise.all(messages); // returning a promise
};
and you could use map:
exports.handler = (event) => { // async is not mandatory here
const messages = event.Records.map(function(messageId, body) {
//options object created from some stuff
return getEndpoint(options)
});
return Promise.all(messages); // returning a promise
};
To understand why this happens, you must dive a bit into lambda's implementation: it will essentially wait for the function stack to be cleared and since you did NOT return anything at all in there, the function stack got empty right after it queued all the stuff - adding a simple return after the await call makes the fn stack to NOT be empty which means lambda will wait for it to be finished.
If you run this on standard node, your function would also return before the promises were finished BUT your node process would NOT exit until the stack was cleared. This is where lambda diverges from stock node.
Simple call to ec2 Describing Security groups and returning the security group ID. Using Async / await, but when logging the return value, I get undefined. I fully admit I'm coming from Python and I've tried my hardest to wrap my brain around async calls. I thought I had it nailed, but I'm obviously missing something.
'use strict';
// Load Modules
const AWS = require('aws-sdk')
//Set the region
AWS.config.update({region: 'us-west-2'});
// Call AWS Resources
const ec2 = new AWS.EC2();
// Get Security Group ID From Event
const getSgIdFromEvent = async (event) => {
var ec2params = { Filters: [{Name: 'tag:t_whitelist',Values[event['site']]}]};
await ec2.describeSecurityGroups(ec2params, function (err, response) {
if (err) {return console.error(err.message)}
else {
var sgId = response.SecurityGroups[0].GroupId;
return sgId;
};
});
};
// MAIN FUNCTION
exports.handler = (event, context) => {
getSgIdFromEvent(event)
.then(sgId => {console.log(sgId)});
}
"sgId" should return the security group ID. It does print out fine in the original function before the return.
Typically if it is an async call you want you handle it similar to this way without using a callback
// Load Modules
const AWS = require('aws-sdk')
//Set the region
AWS.config.update({ region: 'us-west-2' });
// Call AWS Resources
const ec2 = new AWS.EC2();
// Get Security Group ID From Event
const getSgIdFromEvent = async (event) => {
var ec2params = { Filters: [{ Name: 'tag:t_whitelist', Values[event['site']]}] };
try {
const securityGroupsDesc = await ec2.describeSecurityGroups(ec2params).promise();
const sgId = securityGroupsDesc.SecurityGroups[0].GroupId;
//do something with the returned result
return sgId;
}
catch (error) {
console.log('handle error');
// throw error;
}
});
};
// MAIN FUNCTION
exports.handler = (event, context) => {
getSgIdFromEvent(event)
.then(sgId => { console.log(sgId) });
}
however if it doesn't support async you just use the callback to handle the returned data or error without using async function.However Reading into AWS docs you can find that the function ec2.describeSecurityGroups() returns an AWS Request
which has a method promise() that needs to be invoked to send the request and get a promise returned.Note that the try catch here is not needed but good to have in case error occurs during the process.
As I said in the comment, chance are that describeSecurityGroups doesn't return a Promise. Try transforming it explictly in a Promise instead:
const promiseResponse = await new Promise((res, rej) => {
ec2.describeSecurityGroups(ec2params, function (err, response) {
if (err) {return rej(err.message)}
else {
var sgId = response.SecurityGroups[0].GroupId;
res(sgId);
};
})
});
// promiseResponse is now equal to sgId inside the callback
return promiseResponse; // this will work because the function is async
Note: You can drop the else keyword
Here is the code that worked using async / await. Thanks to #Cristian Traina I realized ec2.describeSecurityGroups wasn't returning a promise, it was returning an AWS.Event.
// Get Security Group ID From Event
const getSgIdFromEvent = async (event) => {
console.log('Getting Security Group ID')
var params = { Filters: [{Name: 'tag:t_whitelist', Values
[event['site']]}]};
const describeSG = await ec2.describeSecurityGroups(params).promise();
return describeSG.SecurityGroups[0].GroupId;
};
// Get Ingress Rules from Security Group
const getSgIngressRules = async (sgId) => {
console.log(`Getting SG Ingress rules for ${sgId}`)
var params = { GroupIds: [ sgId]};
try{
const ingressRules = await ec2.describeSecurityGroups(params).promise();
return ingressRules;
}
catch (error) {
console.log("Something went wrong getting Ingress Ruls");
}
};
// MAIN FUNCTION
exports.handler = (event, context) => {
getSgIdFromEvent(event)
.then(sgId => {return getSgIngressRules(sgId);})
.then(ingressRules => {console.log(ingressRules);});
}
I submitted this as the answer now since the getSgIdFromEvent function I have, is only 8 lines and still using the async/await like I was desiring.
What I was missing was the .promise() on the end of the function and returning that promise.
Thanks for all the responses!