My Node Script Hangs after functions are finished - javascript

I'm calling three functions, after the completion of these functions I want my script to close on it's own but it just hangs.
I've tried making the functions async/promise based, closing the database after each 'mongodb' type function, and using process.exit() within a function as a callback to the last called function.
Connecting to the (local - not Atlas) Database:
MongoClient.connect(local, {useNewUrlParser: true, useUnifiedTopology: true}, function(err, db) {
if (err) {
console.log(err)
}
else {
console.log('Connected to MongoDB...')
//Read in data from jsonfiles and store each file's contents into the database : This is where the functions are being called... within a successful connect to the MongoDB
insertJSON(db, jsonfiles, 'requests', jsonfilesSource)
insertJSON(db, issuedfiles, 'issuedLicenses', isssuedfilesSource)
insertLicenses(db)
}
db.close()
})
Function 1:
function insertJSON(db, dirBuf,collection, sourceFolder) {
var database = db.db('license-server')
var collection = database.collection(collection)
fs.readdir(dirBuf, function(err, files) {
if (err) {
console.log(err.message)
}
else {
files.forEach(function(filename) {
var text = fs.readFileSync(sourceFolder + filename);
var filecontents = JSON.parse(text)
//collection.insertOne(filecontents)
collection.findOne({"DisplayTitle" : filecontents.DisplayTitle, "NodeInformation" : filecontents.NodeInformation, "Date": filecontents.Date})
.then(function(result) {
if(result) {
console.log(`An Item could already be in the database: A file is unique if its display title, nodeinformation, and date are different.
the items display title is ${result.DisplayTitle}`)
return
}
else {
collection.insertOne(filecontents)
console.log(`Added ${filecontents.DisplayTitle} to database`)
}
})
.catch(function(error) {
console.log(error)
})
})
}
})
}
Function 2:
function insertLicenses(db) {
// Set up GridFS to import .lic and .licx files into the database
var database = db.db('license-server')
var collection = database.collection('fs.files')
var bucket = new mongodb.GridFSBucket(database);
var dirBuf = Buffer.from('../license-server/private/licenses')
fs.readdir(dirBuf, function(err, files) {
if (err) {
console.log(err.message)
}
else {
files.forEach(function(filename) {
collection.findOne({"filename": filename}).
then(function(result) {
if(result) {
console.log(`The file ${filename} is already in the database`)
return
}
else {
fs.createReadStream('./private/licenses/' + filename).
pipe(bucket.openUploadStream(filename)).
on('error', function(error) {
assert.ifError(error)
}).
on('finish', function() {
console.log(`Uploaded ${filename}`)
})
}
})
})
}
})
// I tried calling db.close() here since this is the last function to be called. No luck.
}
I'm guessing it has something to do with the mongodb functions having their own way to close themselves but I couldn't seem to find what I was looking for in previous attempts to resolve this issue.
The expected result should be the script closing itself, the actual result is a handing script.

All of these database calls are asynchronous -- the result of this code running is to immediately call db.close and then do the work in insertJSON and insertLicenses. If you were to rewrite this to use async/await (and you'd need to update your other functions as well) the db.close call would close the db, and that would allow the script to exit:
await insertJSON(db, jsonfiles, 'requests', jsonfilesSource)
await insertJSON(db, issuedfiles, 'issuedLicenses', isssuedfilesSource)
await insertLicenses(db)
db.close()
https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Introducing
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function

Related

Access Output from MySQL in another file

I want to access the variable "result" from the function which contains the query.
When I want to access it from another file, in which I am trying to work with the output after a POST Request, the variable is declared as "undefined".
This is the file in which i execute the query:
const db = require('../db/connect');
module.exports = {
getID(name){
db.query(`SELECT CWID FROM user WHERE surname = '${name}'`, function(error, result, fields){
if(error) console.log(error);
console.log(result);
});
}
}
And this is the file where I want to work with the data:
router.post('/test', function(req, res){
const data = queries.getID(req.body.name);
console.log(data);
res.render('new test', {title: "test"});
})
Can anybody help me with this?
Here's an example of querying using mysql and async/await. This should do what you would like:
Query file
const db = require('./db/connect');
module.exports = {
getID(name) {
return new Promise((resolve, reject) => {
db.query(`SELECT CWID FROM user WHERE surname = '${name}'`, function(error, result, fields) {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
}
}
Main File
router.post('/test', async function(req, res){
const data = await queries.getID(req.body.name);
console.log("Query result: ", data);
res.render('new test', {title: "test"});
});
The reason your result is undefined in your initial example is that you're using asynchronous i/o (normal in Node.js). By returning a Promise from getID, we can make async. calls easily and with some nice code syntax.

Change Woocommerce API functions to async/await using NodeJS in AWS Lambda

I am looking to change a couple of Woocommerce API calls so each function finishes before the next functions proceeds. However, I am unsure how to do this without breaking the specific Node Woocommerce API code.
https://woocommerce.github.io/woocommerce-rest-api-docs/?javascript#create-an-order-note
How can I change those two Woocommerce POST functions to Async/Await so it works with AWS Lambda?
(I have included an existing await function currently used in AWS Lambda as a reference)
const WooCommerceAPI = require('woocommerce-api');
const path = require('path');
const util = require('util');
exports.handler = async (event, context, callback) => {
// AWAIT EXAMPLE in LAMBDA -- Outputs the /tmp/ contents to the console.
const readdirs = util.promisify(fs.readdir);
await readdirs('/tmp/').then((files) => {
console.log('Check tmp contents')', files);
}).catch((err) => {
console.log(err);
});
// Update Woocommerce order
const WooCommerce = new WooCommerceAPI({
url: process.env.WOOAPI_URL, // Your store URL
consumerKey: process.env.WOOAPI_KEY, // Your consumer key
consumerSecret: process.env.WOOAPI_SECRET, // Your consumer secret
wpAPI: true, // Enable the WP REST API integration
version: 'wc/v3' // WooCommerce WP REST API version
});
//Set order as complete
const status = { status: 'completed' };
WooCommerce.post('orders/' + orderId, status, function (err, states, res) {
if (err) throw err;
else console.log('Update WooCommerce order with status');
});
const data = { note: 'Please check www.example.com for full instructions' };
WooCommerce.post('orders/' + orderId + '/notes', data, function (err, data, res) {
if (err) throw err;
else console.log('Manual WooCommerce Order Note');
});
};
There's a method called postAsync for async calls in WooCommerce, you can try something like:
const status = {
status: 'completed'
};
const ordersResult = await WooCommerce.postAsync(`orders/${orderId}`, status).then((data) => {
console.log('Update WooCommerce order with status');
}, (err) => {
console.log(err);
});
const noteData = {
note: 'Please check www.example.com for full instructions'
};
const notesResult = await WooCommerce.postAsync(`orders/${orderId}/notes`, noteData).then((data) => {
console.log('Manual WooCommerce Order Note');
}, (err) => {
console.log(err);
});
Every method can be used in a promified way just adding Async to the method name.
You can take a look to the docs here: https://github.com/woocommerce/wc-api-node#promified-methods
If it doesn't work, you can always use it stacking calls like this:
const status = {
status: 'completed'
};
WooCommerce.post('orders/' + orderId, status, function(err, states, res) {
if (err) throw err;
console.log('Update WooCommerce order with status');
const data = {
note: 'Please check www.example.com for full instructions'
};
WooCommerce.post('orders/' + orderId + '/notes', data, function(err, data, res) {
if (err) throw err;
console.log('Manual WooCommerce Order Note');
});
});

Wait for AWS SNS publish callback to return a value to calling method

I am attempting to send a text message when a user requests to reset their password. I would like to wait for the message to be sent to alert the user if it was successful or not. I am currently attempting to do it as follows:
async function sendResetPasswordTextMessage(req, res) {
let result = {};
let phoneNumber = req.body.phoneNumber;
if (phoneNumber === undefined) {
return sendInvalidParametersMessage(res);
}
phoneNumber = phoneNumber.toString();
const userProfile = await models.UserProfile.findOne({
where: {
phoneNumber: phoneNumber
}
});
************************** RELEVANT CODE TO ISSUE *************************
if (userProfile) {
const message = "Your username is:\n" + userProfile.username;
const sent = await AWSSNSClient.sendMessage(message, phoneNumber);
if (!sent) {
result.error = setTitleAndMessage("Error", "An error occurred");
} else {
result.success = setTitleAndMessage("Success", "Message sent");
}
}
return res.send(result);
***************************************************************************
}
In my other class AWSSNSClient, I have the following sendMessage function:
function sendMessage(message, phoneNumber) {
const params = {
Message: message,
MessageStructure: "string",
PhoneNumber: "+1" + phoneNumber
};
let sent = false;
sns.publish(params, function(err, data) {
if (err) {
console.log(err, err.stack); // an error occurred
}
else {
sent = true;
}
});
return sent;
}
I cannot figure out how to make sendMessage wait for sns.publish to return before it returns itself. I have tried making it an async method and adding await on sns.publish, but the function still returns before sent gets set to true.
I know that the messages are sending without error because I am receiving them and no console logs are printed.
Stumbled on this one via Google trying to figure this out myself today - short answer that I am now using:
You can now do this with Async/Await — and Call the AWS service (SNS for example) with a .promise() extension to tell aws-sdk to use the promise-ified version of that service function (SNS) instead of the call back based version.
The only caveat here is the containing function must ALSO be async to utilize the await syntax.
For example:
let snsResult = await sns.publish({
Message: snsPayload,
MessageStructure: 'json',
TargetArn: endPointArn
}, async function (err, data) {
if (err) {
console.log("SNS Push Failed:");
console.log(err.stack);
return;
}
console.log('SNS push suceeded: ' + data);
return data;
}).promise();
The important part is the .promise() on the end there. Full docs on using aws-sdk in an async / promise based manner can be found here: https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/using-promises.html
In order to run another aws-sdk task you would similarly add await and the .promise() extension to that function (assuming that is available).
For anyone who runs into this thread and is actually looking to simply push multiple aws-sdk promises to an array and wait for that WHOLE array to finish (without regard to which promise executes first) I ended up with something like this:
let snsPromises = [] // declare array to hold promises
let snsResult = await sns.publish({
Message: snsPayload,
MessageStructure: 'json',
TargetArn: endPointArn
}, async function (err, data) {
if (err) {
console.log("Search Push Failed:");
console.log(err.stack);
return;
}
console.log('Search push suceeded: ' + data);
return data;
}).promise();
snsPromises.push(snsResult)
await Promise.all(snsPromises)
Hope that helps someone that randomly stumbles on this via google like I did!
stackdave will that actually wait?
Necevil "Search push suceeded will get logged twice" because you're mixing calling operations by passing a callback and using promises. You should only use one method of getting the result
let snsResult = await sns.publish({
Message: snsPayload,
MessageStructure: 'json',
TargetArn: endPointArn}).promise()
will do the trick
You can simply use callbacks for that. Modify your sendMessge like this
function sendMessage(message, phoneNumber, cb) {
const params = {
Message: message,
MessageStructure: "string",
PhoneNumber: "+1" + phoneNumber
};
sns.publish(params, cb);
}
then on your main file you can supply callback like this
if (userProfile) {
const message = "Your username is:\n" + userProfile.username;
AWSSNSClient.sendMessage(message, phoneNumber, (err, data) => {
if (err) {
result.error = setTitleAndMessage("Error", "An error occurred");
}
else {
result.success = setTitleAndMessage("Success", "Message sent");
}
res.send(result);
});
}
Here the right updated API, August 2018, Necevil answer send the sms twice.
// using config.env
AWS.config.region = 'eu-west-1';
AWS.config.update({
accessKeyId: process.env.AMAZON_SMS_ID,
secretAccessKey: process.env.AMAZON_SMS_TOKEN,
});
// parameters
let params = {
Message: contentSMS, // here your sms
PhoneNumber: mobile, // here the cellphone
};
const snsResult = await sns.publish(params, async (err, data) => {
if (err) {
console.log("ERROR", err.stack);
}
console.log('SNS ok: ' , JSON.stringify (data));
});
If you're having issues with duplicate SNS messages being sent, I fixed this issue by utilizing examples from AWS:
// Load the AWS SDK for Node.js
var AWS = require('aws-sdk');
// Set region
AWS.config.update({region: 'REGION'});
// Create publish parameters
var params = {
Message: 'MESSAGE_TEXT', /* required */
TopicArn: 'TOPIC_ARN'
};
// Create promise and SNS service object
var publishTextPromise = new AWS.SNS({apiVersion: '2010-03-31'}).publish(params).promise();
// Handle promise's fulfilled/rejected states
publishTextPromise.then(
function(data) {
console.log("Message ${params.Message} send sent to the topic ${params.TopicArn}");
console.log("MessageID is " + data.MessageId);
}).catch(
function(err) {
console.error(err, err.stack);
});
By utilizing a traditional .then() I was able to squash the duplicate message bug mentioned in comments above.
You can create a async function what use the promise method
async function sendMessage(message, phoneNumber){
const params = {
Message: message,
PhoneNumber: phoneNumber
};
return new Promise((resolve, reject) => {
SNS.publish(params, (err, data) => {
if (err) {
console.log("Search Push Failed:");
console.log(err.stack);
return reject(err);
} else {
console.log('Search push suceeded:' + phoneNumber);
return resolve(data);
}
})
});
}
and then you can call
var s= await sendMessage(message,phoneNumber);

node.js: Return from function not acting as expected

I'm very new to javascript/node.js and I'm having trouble with the following code. This is the handler for API an call. The 2nd code segment is just like the 1st, except there is an additional database lookup Merchant.findOne(...), and therefor the 'newTransaction.save()' function is nested one level deeper.
Both code segments return the 'output' variable value correctly. However, the second code segment does NOT also properly save the 'newTransaction' to the Mongo database.
I'm pretty sure the issue has to do with how/when the code returning from newTransaction.save(function (err, transaction){..} but I can't seem to get it straightened out.
I have been looking all over the internet trying to understand and fix this, with no success. Any help is appreciated...
Here is the older, simpler code that works as expected:
handler : function(request, reply) {
var output = {
"success": true,
"operations": [],
"epoch": Date.now()
};
Terminal.findById(request.payload.deviceNumber, function (err, terminal) {
if (err) {
return reply(Boom.internal('Error looking up terminal.', err));
}
if (terminal) {
ticket.quote("bitstamp", "USD", 1, function (err, exchangeRate) {
if (err) {
console.error(err);
return reply(Boom.internal('Error obtaining ticket quote.', err));
}
var newTransaction = new Transaction({
terminal: request.payload.deviceNumber,
merchant: terminal.merchant,
ccExchangeRate: exchangeRate.buy,
fiatAmtDue: request.payload.transactionValue,
ccAmtDue: ccAmtDueTruncated
});
newTransaction.save(function (err, transaction){
if (err) {
return reply(Boom.internal('Error creating new transaction.', err));
}
output.operations.push(
{
"control": "KeyPairGenControl",
"rand": cc.pseudoRandomBytes(32).toString('hex'),
"follow": {
"url": "/pos/v1/AddressAndEncKey",
"post": {
"transactionId": transaction.transactionId
}
}
}
);
return reply(output);
});
});
} else {
return reply(Boom.internal('Error looking up terminal.', err));
}
});
}
Here is the new code that does NOT save the newTransaction data into the Mongo DB.
handler : function(request, reply) {
var output = {
"success": true,
"operations": [],
"epoch": Date.now()
};
Terminal.findById(request.payload.deviceNumber, function (err, terminal) {
if (err) {
return reply(Boom.internal('Error looking up terminal.', err));
}
if (terminal) {
Merchant.findOne({merchantId: terminal.merchant}, function(err, merchant) {
if (err) {
console.log('Cannot find merchant');
return reply(output);
}
var processor = merchant.backendPaymentProcessor.name;
var localCurrency = merchant.localFiatCurrency;
//###################
ticket.quote(processor, localCurrency, 1, function (err, exchangeRate) {
if (err) {
console.error(err);
return reply(Boom.internal('Error obtaining ticket quote.', err));
}
var newTransaction = new Transaction({
terminal: request.payload.deviceNumber,
merchant: terminal.merchant,
ccExchangeRate: exchangeRate.buy,
fiatAmtDue: request.payload.transactionValue,
ccAmtDue: ccAmtDueTruncated
});
newTransaction.save(function (err, transaction){
if (err) {
return reply(Boom.internal('Error creating new transaction.', err));
}
output.operations.push(
{
"control": "KeyPairGenControl",
"rand": cc.pseudoRandomBytes(32).toString('hex'),
"follow": {
"url": "/pos/v1/AddressAndEncKey",
"post": {
"transactionId": transaction.transactionId
}
}
}
);
return reply(output);
});
//return reply(output);
});
//###################
});
} else {
return reply(Boom.internal('Error looking up terminal.', err));
}
});
}
I did a diff of your 2 version:
Check 1
ticket.quote
Callback are identical for both version
processor, localCurrency are different
Is exchangeRate pass into callback correct?
Check 2
newTransaction.save
newTransaction and callback for .save are setup identical
Check(console.log()) the values used in setting up new Transaction({...})
Check transaction object received by callback
Check/debug the code of Transaction.save().
I don't think the issue is with the code you posted. Both version reached return reply(output); inside newTransaction.save's callback. Very likely issue is inside Transaction class or Transaction.save() logic.
One scenario I can think of is when a transaction failed:
Transaction object is available (even for failed transaction)
Transaction Class / Transaction.save() does not write to db because transaction failed
Transaction.save() pass transaction object to callback, but NOT setting err, even when it should.
Mongoose having a feature to specify the collection name under the schema, or as the third argument when declaring the model. Otherwise it will use the pluralized version given by the name you map to the model.
Mongoose official doc having following statement:
Mongoose by default produces a collection name by passing the model name to the utils.toCollectionName method. This method pluralizes the name. Set this option if you need a different name for your collection.
schema-mapped:
new Schema({ <key>: <value>},
{ collection : '<collection name>' }); // collection name
model-mapped:
mongoose.model('<Model name>',
new Schema({ <key>: <value>}),
'<collection name>'); // collection name
You may also find same here

Communicate with C# host from edge.js

I'm trying to put together a quick node.js/edge.js/C# bridge for a demo.
I have to use the ".Net calling Node.js" style, as the existing C# code uses a number of config values, which I can't add to node.exe.config as I will need to run several versions concurrently.
So I have this code:
private static async Task Start() {
Func<object, Task<object>> edge = EdgeJs.Edge.Func(#"
var login = require('login.js');
var edge = require('edge')
login({ email: 'user#example.com', password: 'shh' }, function callback(err, api) {
if (err) return console.error(err);
// This will keep listening until terminated
api.listen(function callback(err, message) {
if (err) return console.error(err);
// At this point I need to send the message back to this class so it can be processed..
console.log(message); // send the message to C#
// ... and then return the response via the api
api.send('response goes here');
});
});
return function (data, callback) {
callback(null, er...);
}
");
}
So, the code is waiting for messages in an event loop and responding. This all works with hardcoded values. But I need to submit the message back to the C# for processing, and I cannot work out how to communicate back and forth between edge.js and the C# app.
It must surely be via the callback, but I can't seem to start figure out how to structure it, and time is getting short. And I'm by no means a JavaScript expert.
How can I communicate between the edge code and the C# code from within the event loop using the callback?
You're right, it's via the callback. Since you're using async code, you have to wrap all your code inside the returned (edge) function, like this:
private static async Task Start() {
Func<object, Task<object>> edge = EdgeJs.Edge.Func(#"
// edge_callback is used to return values to the C# code
return function(data, edge_callback) {
var login = require('login.js');
var edge = require('edge')
login({
email: 'user#example.com',
password: 'shh'
}, function callback(err, api) {
if (err) return console.error(err);
// possible enhancement here by letting C# know there is an error
// edge_callback(err);
// This will keep listening until terminated
api.listen(function callback(err, message) {
if (err) return console.error(err);
// same thing here: edge_callback(err);
// At this point I need to send the message back to this class so it can be processed..
console.log(message); // send the message to C#
// use the callback, first param is error if there is any, second is the data
edge_callback(null, message);
// ... and then return the response via the api
api.send('response goes here');
});
});
}
");
}
I have ended up with something like this: there's a function defined on the data passed to edge, which edge then calls when a new message is received. That function then waits for the response, and passes it back to edge, which receives the result in (of course) another callback.
private static async Task Start() {
dynamic payload = new ExpandoObject();
payload.msgHook = NewMessage;
payload.login = new {
email,
password
};
var receive = Edge.Func(#"
return function(payload, edge_callback) {
var login = require('index.js');
login({
email: payload.login.email,
password: payload.login.password
}, function callback(err, api) {
if (err) {
edge_callback(err);
}
api.listen(function callback(err, message) {
if (err) { edge_callback(err); }
payload.msgHook(message,
function callback(err, result) {
if (err) {
edge_callback(err);
}
var msg = {
body: result.body,
url: result.url
}
api.sendMessage(msg, result.threadId);
});
});
});
}
");
var _ = await receive(payload) as IDictionary<string, object>;
}
private static Func<object, Task<object>> NewMessage {
get {
Func<object, Task<object>> hook = async m => {
string body, threadId;
if (!ProcessMessage(m as IDictionary<string, object>, out body, out threadId)) {
log.Error("Failed to process message: " + m.ToString());
}
api.SendMessage(body, threadId, phone);
var reply = await waitForReply(threadId);
var result = new {
body = reply
};
// Return the _result_ of the task.
return Task.FromResult<object>(result).Result;
};
return hook;
}
}

Categories

Resources