Node.js - howto block around async call. Or non-blocking xmltojs lib - javascript

I'm over my head at the moment.
I'm new to node and writing a passportjs module for Freshbooks. There's a Passport function I'm trying to implement that get's a user's profile.
This code uses Passport's OAuth foo to make a request.
this._oauth.post(url, token, tokenSecret, post_body, post_content_type, function (err, body, res) {
if (err) { return done(new InternalOAuthError('failed to fetch user profile', err)); }
try {
var parser = require('xml2json');
var json = parser.toJson(body); //returns a string containing the JSON structure by default
var util = require('util');
console.log(util.inspect(json));
var profile = { provider: 'freshbooks' };
profile.id = json.response.staff.staff_id;
profile.displayName = json.response.staff.first_name + ' ' + json.response.staff.last_name;
profile.name = { familyName: json.response.staff.last_name,
givenName: json.response.staff.first_name };
if (json.response.staff.email) { profile.emails = [{ value: json.response.staff.email }]; }
profile._raw = body;
profile._json = json;
console.log(util.inspect(json));
done(null, profile);
} catch(e) {
done(e);
}
});
I get a response. It's xml. I'm converting it to JSON, but I don't want that actually. I want a plain-old javascript object.
I looked at https://github.com/Leonidas-from-XIV/node-xml2js but the examples don't show how to get the result out.
var parseString = require('xml2js').parseString;
var xml = "<root>Hello xml2js!</root>"
parseString(xml, function (err, result) {
console.dir(result);
});
What do I do to block around this code till the call is complete and get result out? I'm not sure how to merge these two callbacks together.

you can ask xml2json to return object:
var json = parser.toJson(body, {object: true});
if you decide to use async parser then just put your done callback inside json result handler. There is no need to "block" async function:
var parseString = require('xml2js').parseString;
parseString(body, function(err, json) {
// handle error: return done(err)
// do your logic if no error
// ...
// profile._json = json;
// ...
//
// 'return' result
done(null, profile);
});

Related

How to wait for a variable to be populated by an api request before passing it to a webpage as an argument?

I'm new to JavaScript and cannot seem to make this work , the topic of quiz depends on the user input... when the user presses next , I get the topic (this also takes user to the main quiz page), then i have to fetch data from the api with the topic as a parameter... I have to process the result of the fetch operation.. Then I have to pass that info to to the main quiz page... but the variable that is supposed to be populated by the fetch request is still undefined when i pass is to the main quiz page
var Allquestions;
var sheetdb = require('sheetdb-node');
// create a config file
var config = {
address: 'https://sheetdb.io/api/v1/9djmf8ydc7hwy',
};
//sheetdb
// Create new client
var client = sheetdb(config);
function downloadquestions(topic) {
console.log(topic);
client.read({ limit: 2, sheet: topic }).then(function(data) {
console.log(data + " in client.read func")
processQuestions(data);
}, function(err){
console.log(err);
});
}
async function processQuestions(data) {
console.log(data + "data in process");
Allquestions = JSON.parse(data);
console.log(Allquestions[0].Question + " This is defined");
}
app.get("/", (req, res) => {
res.render("pages/index", { title: "Home"});
});
// app.post("/" , urlencodedParser ,(req , res) => {
// console.log(req.body.topic);
// })
app.get("/questions", urlencodedParser , (req , res) => {
downloadquestions(req.body.topic);
console.log(Allquestions + " this is undefined");
res.render("/pages/quizpage" , {Allquestions})
})
There are a few issues with your code, you have a broken promise chain, client.read( is a promise, and that promise is going nowhere. You either return it, or await it. To be able to await your will need to also mark your route (req, res) as async too.
Your code is a little mixed up, you have Allquestions as a global var, this isn't great for multi-user, as the last topic is going to override this each time.
Also try and avoid swallowing exceptions in utility functions, try and keep your exception handling at the top level, eg. in your case inside your req/res handler.
So with all this in mind, your refactored code could look something like ->
const sheetdb = require('sheetdb-node');
// create a config file
const config = {
address: 'https://sheetdb.io/api/v1/9djmf8ydc7hwy',
};
//sheetdb
// Create new client
const client = sheetdb(config);
async function downloadquestions(topic) {
const data = await client.read({ limit: 2, sheet: topic });
return processQuestions(data);
}
function processQuestions(data) {
return JSON.parse(data);
}
app.get("/", (req, res) => {
res.render("pages/index", { title: "Home"});
});
app.get("/questions", urlencodedParser , async (req , res) => {
try {
const allQuestions = await downloadquestions(req.body.topic);
res.render("/pages/quizpage" , {Allquestions});
} catch (e) {
console.error(e);
res.end('There was an error');
}
})

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.

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);

NodeJS MySQL Async- Return object back to caller function

I am brand new to NodeJS and am struggling to find a solution to making a reusable function to execute a query (passed via a parameter) and then simply return the response to the caller. I want to do it this way as there will be over 100 functions requiring database queries and it'll help reduce code size.
I’ve tried the following as a very primitive test, but the dbA_Read_Data() function always returns undefined, because it's asynchronous. However, 6 days later, and over 15 alternative callback/promise solutions that work for some haven't worked for me and I am no closer to solving this issue and accomplishing the following.
const { Databases } = require('../../utils/enums');
var mysql = require('mysql');
function getAllUsers() {
//This function would be called when an endpoint is hit in my HapiJS system.
var myQuery = "SELECT * FROM Users WHERE Account_Status== 'ACTIVE' ";
var response = dbA_Read_Data(myQuery);
return response;
}
//dbA => website database, contains users/permissions/etc
//Other databases... you get the idea
function dbA_Read_Data(query) {
dbConn = createConnection("localhost", Databases.db_A.name, Databases.db_A.username, Databases.db_A.password);
dbConn.connect();
dbConn.query(query, function(err, rows, fields) {
if (err) throw err;
var myResponseObject = {
query: query,
data: {
count: rows.length,
records: rows
}
}
});
}
function createConnection(host, database, user, password) {
var connection = mysql.createConnection({
host: host,
user: user,
password: password,
database: database
});
return connection;
}
How do I get the function dbA_Read_Data(query) to return the response object to the caller function?
As in the comments section, the database call conn.query is asynchronous so it is impossible to simply return the result. In order to obtain the result of this query and operate on it, you could use a Promise.
I suppose your dbA_Read_Data(query) function could look like that:
function dbA_Read_Data(query){
var dbConn = createConnection("localhost", Databases.db_A.name, Databases.db_A.username, Databases.db_A.password);
dbConn.connect();
return new Promise(function(resolve, reject){
dbConn.query(query, function(err, rows, fields) {
if ( err ) {
reject(err);
} else {
var myResponseObject = {
query: query,
data: {
count: rows.length,
records: rows
}
}
resolve(myResponseObject);
}
});
});
}
Then your getAllUsers() function call would require .then() in order to obtain the results:
function getAllUsers(){
var myQuery = "SELECT * FROM Users WHERE Account_Status== 'ACTIVE' ";
return dbA_Read_Data(myQuery).then(function(result){
return result;
}).catch(function(error){
// handle the error properly here...
console.log('error in database operation');
});
}
getAllUsers().then(function(users){
// do something with the result...
console.log(users);
});

Get note contents with evernote api in node.js server

I'm working on my private project using Evernote API and node.js(express.js)
I successfully got an requestToken and accessToken using jsOAuth Module and I got the note guid from noteMetaData too.
however, when I use
noteStore.getNote
function, It continuously fails.
with this errormessage :
Error in NodeBinaryHttpTransport.flush: Binary protocol does not support synchronous calls
however I already inserted my callback function.
I checked API Docs but not specific doc for javascript. Arguments are different. I checked what kind of arguments does noteStore.getNote wants by logging Function.length, but it was 0.
here is my code.
I use express and router is require('express').Router();
gb is global object that contains my developer token and secret.
router.get('/users',function (request,response){
var parsedUrl = url.parse(request.url);
console.log('search is:'+parsedUrl.search)
client.getAccessToken(
gb.oauthToken,
gb.oauthSecret,
getOauthVerifier(parsedUrl.search),
function(error, oauthAccessToken, oauthAccessTokenSecret, results) {
if(error) {
console.log("error\n\n\n");
console.log(error);
}
else {
console.log('successfully get an access token.');
var accessedClient = new Evernote.Client({
token: oauthAccessToken,
sandbox:true
})
var noteStore = accessedClient.getNoteStore("https://sandbox.evernote.com/edam/note/");
noteStore.listNotebooks(function(err, notebook){
var filter = new Evernote.NoteFilter();
filter.notebookGuid = notebook[0].guid;
console.log('filter is..',filter);
var resultSpec = new Evernote.NotesMetadataResultSpec();
resultSpec.includeTitle = true;
resultSpec.includeContentLength = true;
resultSpec.includeCreated = true;
resultSpec.includeAttributes = true;
noteStore.findNotesMetadata(filter, 0, 100, resultSpec, function(err, notesMeta) {
if (err) {
console.error('err',err);
}
else {
console.log("Found "+notesMeta.notes.length+" notes in your default notebook . . .")
for (var i in notesMeta.notes) {
var noteGuid = notesMeta.notes[i]['guid'];
var note = noteStore.getNote(
noteGuid,{
withContent: true
},
function(err, results){
if(err) return console.error("Error")
console.log(results);
response.end();
})
}
}
});
})
}
);
})
It was a stupid question. I found a solution.
All I Needed to do was sticking to the parameter guided in the API docs.
noteGuid,{
var note = noteStore.getNote( true, true, true,true
},
function(err, results){
if(err)
return console.error("Error")
console.log(results);
response.end();
})

Categories

Resources