AWS Lambda function global variables - javascript

I am writing an AWS Lambda function in JavaScript (Node.js) that interacts with CodeCommit through the AWS SDK.
The communication between the services works as expected, I am getting data within the CodeCommit function, but the issue I am facing appears when I want to use this data outside of the function.
I have tried two approaches:
1. Global Variable
Code:
var aws = require('aws-sdk');
var codecommit = new aws.CodeCommit({ apiVersion: '2015-04-13' });
var repoName = ''; // Declared my global variable here
exports.handler = function(event, context) {
var commitId = "69a5f8eeba340d71ba41b8f20d77cc20b301ff52"
var repository = "my-repository"
var params = {
repositoryName: repository
};
codecommit.getRepository(params, function(err, data) {
if (err) {
console.log(err);
var message = "Error getting repository metadata for repository " + repository;
console.log(message);
context.fail(message);
} else {
console.log('Repository Name:', data.repositoryMetadata.repositoryName); // Shown with data
repoName = data.repositoryMetadata.repositoryName; // Setting the variable
console.log('Account Id:', data.repositoryMetadata.accountId); // Shown with data
}
});
console.log(repoName); // Shown as blank in the output
};
Output:
The last written "console.log" is the first to print in the execution results, but shows blank. The two other console.log (within the functions) are then printed, and they show the data.
2. Function
Code:
var aws = require('aws-sdk');
var codecommit = new aws.CodeCommit({ apiVersion: '2015-04-13' });
exports.handler = function(event, context) {
var commitId = "69a5f8eeba340d71ba41b8f20d77cc20b301ff52"
var repository = "my-repository"
var repoData = getRepository(repository)
console.log('Repository Name:', repoData.repositoryName);
console.log('Account Id:', repoData.accountId);
};
function getRepository(repository) {
var params = {
repositoryName: repository
};
codecommit.getRepository(params, function(err, data) {
if (err) {
console.log(err);
var message = "Error getting repository metadata for repository " + repository;
console.log(message);
context.fail(message);
} else {
var repoData = {};
repoData.repositoryName = data.repositoryMetadata.repositoryName;
repoData.accountId = data.repositoryMetadata.accountId;
console.log(repoData); // Shows output in execution results when lines 11 & 12 are commented
return repoData;
}
});
}
Output:
{
"errorType": "TypeError",
"errorMessage": "Cannot read property 'repositoryName' of undefined",
"trace": [
"TypeError: Cannot read property 'repositoryName' of undefined",
" at Runtime.exports.handler (/var/task/index.js:57:46)",
" at Runtime.handleOnce (/var/runtime/Runtime.js:66:25)"
]
}
Conclusion
None of those approaches worked. The data is always visible within the function but never outside of it. I suspect that the code outside of the function executes before the function itself, and I wonder if I could make the code to wait that the function has been executed before doing the console.log (and other actions after it). Or maybe I am wrong on another level?

You are using a callback model, in which case the console.log in the first example is being hit before the code in the callback. A better option would be to use async/await.
var aws = require('aws-sdk');
var codecommit = new aws.CodeCommit({ apiVersion: '2015-04-13' });
var repoName = ''; // Declared my global variable here
exports.handler = async function(event, context) {
var commitId = "69a5f8eeba340d71ba41b8f20d77cc20b301ff52"
var repository = "my-repository"
var params = {
repositoryName: repository
};
var data = await codecommit.getRepository(params).promise();
console.log('Repository Name:', data.repositoryMetadata.repositoryName); // Shown with data
repoName = data.repositoryMetadata.repositoryName; // Setting the variable
console.log('Account Id:', data.repositoryMetadata.accountId); // Shown with data
console.log(repoName);
};
Notice that I'm not catching the error here, but if you wanted to you can use a try/catch block. Just be sure you throw a new error in that case if you want the function to fail.

Related

Watson Assistant context is not updated

I use watson assistant v1
My problem is that every time I make a call to the code in Nodejs, where I return the context, to have a coordinated conversation, the context is only updated once and I get stuck in a node of the conversation
this is my code
client.on('message', message => {
//general variables
var carpetaIndividual = <../../../>
var cuerpoMensaje = <....>
var emisorMensaje = <....>
//detect if context exists
if(fs.existsSync(carpetaIndividual+'/contexto.json')) {
var watsonContexto = require(carpetaIndividual+'/contexto.json');
var variableContexto = watsonContexto;
} else {
var variableContexto = {}
}
//conection with Watson Assistant
assistant.message(
{
input: { text: cuerpoMensaje },
workspaceId: '<>',
context: variableContexto,
})
.then(response => {
let messageWatson = response.result.output.text[0];
let contextoWatson = response.result.context;
console.log('Chatbot: ' + messageWatson);
//Save and create JSON file for context
fs.writeFile(carpetaIndividual+'/contexto.json', JSON.stringify(contextoWatson), 'utf8', function (err) {
if (err) {
console.error(err);
}
});
//Send messages to my application
client.sendMessage(emisorMensaje, messageWatson)
})
.catch(err => {
console.log(err);
});
}
client.initialize();
the context.json file is updated, but when it is read the code only reads the first update of the context.json file and not the other updates
This will be because you are using require to read the .json file. For all subsequent requires of an already-required file, the data is cached and reused.
You will need to use fs.readfile and JSON.parse
// detect if context exists
if (fs.existsSync(carpetaIndividual+'/contexto.json')) {
var watsonContexto = fs.readFileSync(carpetaIndividual+'/contexto.json');
// Converting to JSON
var variableContexto = JSON.parse(watsonContexto);
} else {
var variableContexto = {}
}
There is another subtle problem with your code, in that you are relying on
your async call to fs.writeFile completing before you read the file. This will be the case most of the time, but as you don't wait for the fs.writeFile to complete there is the chance that you may try to read the file, before it is written.

Create File in Google Cloud Bucket from Firebase Cloud Function

I have a function that monitors a node in a Realtime database and once a new child is written to the node the function simply needs to create a html document in a Google Cloud bucket. The HTML document will have a unique name and will contain some data from the node. It's all fairly straightforward, however I can't actually create and write to the document. I've tried 3 methods so far (outlined in the code below), none of these methods work.
const {Storage} = require('#google-cloud/storage');
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const fs = require('fs');
const {StringStream} = require('#rauschma/stringio')
const instanceId = 'my-project-12345';
const bucketName = 'my-bucket';
exports.processCertification = functions.database.instance(instanceId).ref('/t/{userId}/{testId}')
.onCreate((snapshot, context) => {
const dataJ = snapshot.toJSON();
var testResult = "Invalid";
if(dataJ.r == 1) {testResult = "Positive";}
else if(dataJ.r == 2) {testResult = "Negative";}
console.log('Processing certificate:', context.params.testId, testResult);
var storage = new Storage({projectId: instanceId});
const fileName = context.params.testId + '.html';
const fileContents = "<html><head></head><body>Result: " + testResult + "</body></html>"
const options = {resumable:false, metadata:{contentType:'text/html'}};
const bucket = storage.bucket(bucketName);
const file = bucket.file(fileName);
console.log('Saving to:' + bucketName + '/' + fileName);
if(false) {
// Test 1. the file.save method
// Errors with:
// (node:2) MetadataLookupWarning: received unexpected error = URL is not defined code = UNKNOWN
file.save(fileContents, options, function(err) {
if (!err) {console.log("Save created object at " + bucketName + "/" + fileName);}
else {console.log("Save Failed " + err);}
});
} else if(true) {
// Test 2. the readStream.pipe method
// No errors, doesn't output error message, doesn't output finish message, no file created
fs.createReadStream(fileContents)
.pipe(file.createWriteStream(options))
.on('error', function(err) {console.log('WriteStream Error');})
.on('finish', function() {console.log('WriteStream Written');});
} else {
// Test 3. the StringStream with readStream.pipe method
// Errors with:
// (node:2) MetadataLookupWarning: received unexpected error = URL is not defined code = UNKNOWN
const writeStream = storage.bucket(bucketName).file(fileName).createWriteStream(options);
writeStream.on('finish', function(){console.log('WriteStream Written');}).on('error', function(err){console.log('WriteStream Error');});
const readStream = new StringStream(fileContents);
readStream.pipe(writeStream);
}
console.log('Function Finished');
return 0;
});
In all cases the "Processing certificate" and "Saving to" outputs appear, I also get the "Function Finished" message every time. The errors (or in one case no response) is written against each of the tests in the code.
My next step will be to create the file locally and then use upload() method, however each of these methods seem like they should work, plus the only error message I have is talking about URL errors so I suspect trying to use upload() method would run into the same problems as well.
I'm using Node.JS v8.17.0 and the following packages
"dependencies": {
"#google-cloud/storage": "^5.0.0",
"#rauschma/stringio": "^1.4.0",
"firebase-admin": "^8.10.0",
"firebase-functions": "^3.6.1"
}
Any advice is most welcome
In each case, you are not working with promises correctly. For database triggers (and all other background triggers), you must return a promise that resolves when all of the asynchronous work is complete in a function. Right now, you're not doing anything at all with promises, while each of the APIs you're calling are all asynchronous. Your function is just returning 0 immediately without waiting for the upload to complete, and Cloud Functions is simply terminating and cleaning up before anything can happen.
I suggest choosing one of the methods that returns a promise with the upload is complete (probably file.save()), then return that promise from the function.

NodeJS/ES6: Cannot set property of undefined

Using NodeJS/ES6 I have created a MongoDB connector class.
class DBClient {
constructor(host, port) {
this.host = host;
this.port = port
this.dbConnection = null;
}
buildConnectionString() {
return 'mongodb://' + this.host + ':' + this.port;
}
connect() {
var connectionString = this.buildConnectionString();
console.log('[MongoDB] - Connecting to instance # ' + connectionString);
var DBConnection = MongoClient.connect(connectionString, function(error, db) {
if (error) {
console.log('[MongoDB] - Error connecting to instance');
console.log(error);
}
else {
console.log('[MongoDB] - Connection Successful');
this.dbConnection = db;
}
});
}
}
Which is then being created in a different file like so
var client = new DBClient('127.0.0.1', '1337');
client.connect();
When the database is connected to, NodeJS crashes when it reaches this.dbConnection = db;, stating TypeError: Cannot set property 'dbConnection' of undefined.
I'm pretty sure it has something to do with being used in a callback, which is screwing up the scope. How can I get around this though? Wouldn't any operation from the callback scope be isolated and unable to reference this?
Also, as a side question, is this a bad code practice to initialize a null property like I'm doing in the constructor? If so, what would be a more proper way of doing it?
Indeed if you want to keep your scope use lambda instead like :
var DBConnection = MongoClient.connect(connectionString, (error, db) =>
{
...
});
if you have to keep your function because of your transpilation settings or the lib does not support lambda, save your scope in a variable like :
var self = this;
var DBConnection = MongoClient.connect(connectionString, function(error, db)
{
... self.dbConnection = db;
});

(Javascript Node.js) How to get varibles from a IIFE

Please see my code below:
I am trying to assign the recordset to a variable, can use index.js to call this variable out.
I am able to console.log the recordset. But when I call this IIFE, it is always says undefined.
var mssql = require('mssql');
var dbcon = require('./dbcon');
var storage = (function () {
var connection = new mssql.Connection(dbcon);
var request = new mssql.Request(connection);
connection.connect(function (recordset) {
request.query('select getdate()', function (err, recordset) {
console.dir(recordset);
});
connection.close();
});
})();
module.exports = storage;
index.js
var storage = require('./storage');
"AMAZON.HelpIntent": function (intent, session, response) {
storage(function (recordset){
var speechOutput = 'Your result is '+recordset;
response.ask(speechOutput);
});
However, I can't get the recordset. I got "Your result is {object, object}. "
that's because the IIFE is executing right away, try returning a function instead and then executing that function when you import that module,
var storage = (function(mssql, dbcon) {
return function() {
var connection = new mssql.Connection(dbcon);
var request = new mssql.Request(connection);
connection.connect(function(recordset) {
request.query('select getdate()', function(err, recordset) {
console.dir(recordset);
});
connection.close();
});
}
})(mssql, dbcon);
and I don't understand why you need the IIFE, why don't you just assign the function to the variable?
If you're trying to assign the variable "recordset" to "storage" then this will never work as "connection.connect" is an asynchronous function, and in that case you should think about callback functions or promises.
Update
Based on your request, here's an implementation with a callback function and how it's used
var mssql = require('mssql');
var dbcon = require('./dbcon');
var storage = function(callback) {
var connection = new mssql.Connection(dbcon);
var request = new mssql.Request(connection);
connection.connect(function(recordset) {
request.query('select getdate()', function(err, recordset) {
if(!err && callback){
callback(recordset);
}
connection.close();
});
});
}
module.exports = storage;
// --------------------------------------------------
// implementation in another module
var storage = require("module_path"); // (1)
var answer;
storage(function(recordset){ // (2)
answer = recordset;
console.log(answer); // actual data, (3)
// implement your logic here
});
console.log(answer); // undefined (4)
// --------------------------------------------------
How this code works:
- You start by calling the storage method and sending it a callback method.
- The whole point of the callback function is that you won't wait for the result, your code will actually continue working at the same time that the storage method is connecting to the database and trying to get the data, ans since db operations are much slower, line(4) will execute before line(3).
- The flow of work will be as follows:
line (1)
line (2)
line (4)
line (3) at sometime in the future when the data is retrieved from database
- To see this more clearly, try doing this at the last line,
setTimeout(function(){console.log(answer);}, 3000);
This will wait for sometime until the data comes back;

DocumentDB connection in js: "require is not defined"

I've been able to create the database and query it. Using Microsoft's tutorial on using node.js to query, I have been able to accomplish this with this code:
// Simple Query
"use strict";
var documentClient = require("documentdb").DocumentClient;
var config = require("./config");
var url = require('url');
// use the previously saved config.endpoint and config.primaryKey to create a new DocumentClient
var client = new documentClient(config.endpoint, { "masterKey": config.primaryKey });
// These urls are how the DocumentDB client will find the right database and collection.
var HttpStatusCodes = { NOTFOUND: 404 };
var databaseUrl = `dbs/${config.database.id}`;
var collectionUrl = `${databaseUrl}/colls/${config.collection.id}`;
// Query JSON document collection
function queryCollection() {
console.log(`Querying collection through index:\n${config.collection.id}`);
return new Promise((resolve, reject) => {
client.queryDocuments(
collectionUrl,
'SELECT VALUE gd.NFL FROM GamblersDenDB gd WHERE gd.id = "SanDiego"'
).toArray((err, results) => {
if (err) reject(err)
else {
for (var queryResult of results) {
let resultString = JSON.stringify(queryResult);
console.log(`\tQuery returned ${resultString}`);
}
console.log();
resolve(results);
}
});
});
};
queryCollection()
Running that js file in my command prompt works! It results in the output:
C:\Users\kenv\Desktop\DocDB Test>node SimpleQuery.js
Querying collection through index:
GamblersDenColl
Query returned {"ID":"SDC","name":"Chargers"}
Great. So now I've transferred my code to my project's folder and try to run in the app with
taco run android --device
When I pull up the console, the first error that sticks out to me is
Uncaught ReferenceError: require is not defined(…)
It's pointing to the line var documentClient = require("documentdb").DocumentClient; in my code.
Here is my complete js file code that's throwing the error:
(function () {
"use strict";
document.addEventListener( 'deviceready', onDeviceReady.bind( this ), false );
function onDeviceReady() {
navigator.splashscreen.hide();
console.log("Cordova is READY!");
// Handle the Cordova pause and resume events
document.addEventListener( 'pause', onPause.bind( this ), false );
document.addEventListener( 'resume', onResume.bind( this ), false );
$(".btnURL").on("click", function(){loadURL($(this))});
function loadURL(theObj) {
cordova.InAppBrowser.open(theObj.data("url"), "_blank", "location=yes");
}
//********* jQuery VARIABLES ***************//
var $elBtnSaveName= $("#btnSaveName"),
$elShowClients= $("#btnShowClients"),
$elDivShow= $("#divShow"),
$elFormClient= $("#formClient");
//********** EVENT HANDLERS *****************//
$elShowClients.on("click", queryCollection);
//********************* DOCUMENT DB SECTION *********************************************************/
var documentClient = require("documentdb").DocumentClient;
var config = require("./config");
var url = require('url');
// use the previously saved config.endpoint and config.primaryKey to create a new DocumentClient
var client = new documentClient(config.endpoint, { "masterKey": config.primaryKey });
// These urls are how the DocumentDB client will find the right database and collection.
var HttpStatusCodes = { NOTFOUND: 404 };
var databaseUrl = `dbs/${config.database.id}`;
var collectionUrl = `${databaseUrl}/colls/${config.collection.id}`;
// Query JSON document collection
function queryCollection() {
console.log(`Querying collection through index:\n${config.collection.id}`);
return new Promise((resolve, reject) => {
client.queryDocuments(
collectionUrl,
'SELECT VALUE gd.NFL FROM GamblersDenDB gd WHERE gd.id = "SanDiego"'
).toArray((err, results) => {
if (err) reject(err)
else {
for (var queryResult of results) {
let resultString = JSON.stringify(queryResult);
console.log(`\tQuery returned ${resultString}`);
}
console.log();
resolve(results);
fnShowClientsTable(result.rows);
}
});
});
};
function fnShowClientsTable(data){
var str = "<p><table id='tableResults'";
str += "<tr><th>ID</th><th>Name</th><th class='thEmpty'> </th></tr>" //added class to <th> for formatting
for(var i = 0; i < data.length; i++) { // For X number of times worth of data...
str += "<tr><td>" + data[i].doc.ID +
"</td><td>" + data[i].doc.name +
"</td><td class='btnPencil'>✎</td></tr>";
}
str += "</table></p>"; // END table
$elDivShow.html(str); //Show string as HTML on screen
} // END fnShowClientsTable
//************************* END DOCUMENT DB SECTION ******************************************************/
}; // END onDeviceReady()
function onPause() {
// TODO: This application has been suspended. Save application state here.
};
function onResume() {
// TODO: This application has been reactivated. Restore application state here.
};
} )();
I realize my other function to push it to a string for display in HTML is probably wrong (which I will most certainly have another post about that one :)) but right now I'm trying to determine how I can get past this first error.

Categories