I am new to node and writing a small application. I haven't used a language as asynchronous as this on the server before and have myself in a bit of a pickle. I need to take a string, query a table for an id, then insert in a second table using the result, then return a string from the funtion two levels up. I have a custom dao I use for the db stuff. Here is the function where it all happens:
function generateToken(data, userId, client) {
var random = Math.floor(Math.random() * 100001);
var sha256 = crypto.createHmac("sha256", random );
var token = sha256.update(data).digest("base64");
var query = dao.select(
'auth.apps',
{
name: client.name,
version: client.version,
subversion: client.subversion,
patch: client.patch
}
).done(
function(result) {
dao.insert(
'auth.tokens',
{
user_id:userId,
app_id: result.rows[0].id,
token:token
}
);
return "mmmm yellllo";
}
);
var ret_val = await(query);
console.log("Token return: " + ret_val);
return ret_val;
}
and here is the relevant part of my dao for select:
dbo.prototype.select = function(table, where, order_by) {
var where_clause = this.construct_where(where);
var sql = 'SELECT * FROM ' + table + ' WHERE ' + where_clause;
if(order_by !== undefined) {
sql = sql + ' ORDER BY ' + order_by;
};
var result = this.pool.query(sql);
return result;
};
and insert:
dbo.prototype.insert= function(table, values) {
var key_list='', value_list = '';
for( var k in values)
{
key_list = key_list + ', ' + k;
value_list = value_list + ", '" + values[k] + "'";
}
// chop off comma space
key_list = key_list.substring(2);
value_list = value_list.substring(2);
var sql = 'INSERT INTO ' + table + '(' + key_list + ') VALUES(' + value_list + ') RETURNING id';
var result = this.pool.query(sql).catch(function(error) {
console.log("SQL:" + sql + " error:" + error);
});
return result;
};
How do unwind the double promise. I want the generateToken function to return the token variable but only after the insert query has finished.
There is a library named deasync.
And the motivation to create it was to solve the situations when
API cannot be changed to return merely a promise or demand a callback
parameter
So this is the primary and probably the only use case. Because in general Node.js should stay async.
To do the trick you basically should write a function that accepts a callback and then wrap it with deasync as follows:
var deasync = require('deasync');
//It can still take the params before the callback
var asyncGenerateToken = function (data, userId, client, callback) {
var token = 'abc';
//Async operation starts here
setTimeout(function () {
//Async operation is finished, now we can return the token
//Don't forget that the error is 1st arg, data is the 2nd
callback(null, token);
}, 1000);
};
var generateToken = deasync(asyncGenerateToken);
//We'll retrieve a token only after a second of waiting
var token = generateToken('my data', 'my user id', 'my client');
console.log(token);
Hope this helps.
Related
I'm trying to run a NodeJS code that reads some data fields from an array, use them to do a database query to check if the data is duplicate before inserting them into the corresponding table.
My NodeJS code will be called from a PHP script so I need to know when it ends this is why I need to add process.exit(0) somewhere. The problem I have is that if I add it, the script is terminated and my promise never gets the time to send back the result.
Here is my code:
var bar = new Promise((resolve, reject) => {
result.forEach((row, index, array) => {
var escaped = _.map(row, mysql.escape);
var checkQuery = "SELECT COUNT(*) as found FROM data WHERE field1 = " + escaped[0] + " AND field2 = " + escaped[1] + " AND field3 = " + escaped[2] + " AND field4 = " + escaped[3] + " AND field5 = " + escaped[4] + " AND field6 = " + escaped[5] + " AND field7 = " + escaped[6] + ";";
conn.query(checkQuery, function (err, res) {
if (err) {
console.log("Error checking row for duplicate");
console.log(checkQuery);
process.exit(1);
} else {
if (res[0].found == 0) {
var query = " (";
var escaped = _.map(row, mysql.escape);
var csv = escaped.join(',');
query += csv;
query += ")";
query += row !== _.last(result) ? ',' : ';';
console.log(query);//This will change to inserting the data to the table
}else{
console.log("Duplicate found!");
}
}
});
if (index === array.length -1) resolve();
});
});
bar.then(() => {
console.log('All done!');
process.exit(0);
});
If I remove process.exit(0); I see "All done" first then console.log(query) result.
If I add it, the script is terminated and I see "All done" only.
Is there a better approach to do this task please?
Thanks.
Here is a way to wait for a promise before the application exits.
class Waiter {
private timeout: any
constructor() {
this.waitLoop()
}
private waitLoop():void {
this.timeout = setTimeout(() => { this.waitLoop() }, 100 * 1000)
}
okToQuit():void {
clearTimeout(this.timeout)
}
}
// Your app.
const appPromise:Promise<any> = ...
const w = new Waiter()
appPromise.finally(() => {
console.log("Quitting")
w.okToQuit()
})
Running multiple asynchronous operations in a loop and tracking when everything is done is just way, way, way easier if you use promises for all the individual asynchronous operation rather than trying to track asynchronous operations that use plain callbacks.
You don't say exactly what your database is, but if it's mysql, then there is a mysql2/promise driver that natively supports promises and that would be my recommendation to switch to that. Then you can directly use a promise returned from .query(). But, without the info about your specific database driver, I've shown how to manually promisify .query().
Then, the looping code can use a for loop and await to sequence the database calls so it's easy to know when they are all complete.
const { promisify } = require('util');
async function someFunc() {
// other code here
// promisify conn.query (or use promise interface directly from the database)
conn.queryP = promisify(conn.query);
try {
for (const row of result) {
const escaped = _.map(row, mysql.escape);
const checkQuery = "SELECT COUNT(*) as found FROM data WHERE field1 = " + escaped[0] + " AND field2 = " +
escaped[1] + " AND field3 = " + escaped[2] + " AND field4 = " + escaped[3] + " AND field5 = " +
escaped[4] + " AND field6 = " + escaped[5] + " AND field7 = " + escaped[6] + ";";
let res = await con.queryP(checkQuery);
if (res[0].found == 0) {
const csv = _.map(row, mysql.escape).join(',');
const terminator = row !== _.last(result) ? ',' : ';';
const query = " (" + csv + ")" + terminator;
console.log(query); //This will change to inserting the data to the table
} else {
console.log("Duplicate found!");
}
}
} catch (e) {
console.log("Error checking row for duplicate: ", checkQuery);
console.log(e);
process.exit(1);
}
console.log('All done!');
process.exit(0);
}
The code appears to be trying to build a query inside the loop where each iteration of the loop will add-on to the next (that's what _.last(result) ? ',' : ';'; look like anyway). If that's the case, then the query variable has to be moved outside the loop so it can build from one iteration of the loop to the next. But, you don't show what you're really trying to do with that query so you're on your own for that part.
you decide how many promises will go out before hand and then count them as they resolve, then exit
in this example the same principle is applied but it has callback functions instead of promises. For promises you would call a count function from the .then() or .finally(), and the count function will decide whether it is time to exit
mongoose example from a javascript server:
let g_DB = null;
//init mongoose
const mongoose = require("mongoose");
const connectionParams = {
useNewUrlParser: true,
useUnifiedTopology: true,
};
const connStr1 = "mongodb+srv://XX:XX#clusterXX.XX.mongodb.net/XX?
retryWrites=true&w=majority";
mongoose.set("strictQuery", false);
mongoose.connect(connStr1, connectionParams)
.then(handleConnection())
.catch((err) => console.log("Error:", err));
//end script
//handleConnection - start on successful response from mongoose connection
function handleConnection(msg) {
console.log("mongoose has connected to Mongo Atlas successfully");
g_DB = mongoose.connection;
g_DB.once("open", function () {
console.log(
"mongoose has connected to Mongo Atlas Cluster using database XX"
);
doTest();
});
}
//---------------------------------------------------
function doTest() {
console.log("test-05: create 500 books");
//---- MODEL ----
const _schema = new mongoose.Schema({
name: String,
price: Number,
quantity: Number,
});
//g_DB is a mongoose connection set earlier in the script
const _model = g_DB.model("book_schema", _schema, "bookstore");
let loopcount = 500;
let waitcount = loopcount;
for (let i = 0; i < loopcount; i++) {
_m = new _model({
name: `WHY MAKE 500 BOOKS ${new Date().toISOString()}`,
price: 200,
quantity: 2000,
});
_m.save((e, x) => {
if (e) return console.error(e);
console.log(x, `waitcount: ${--waitcount}`);
if (!waitcount) doExit();
});
}
}
//--
function doExit() {
console.log("exit from server");
process.exit();
}
Use Reject/Resolve to manage promise in Nodejs
When your task fulfils your request send result with resolve(); and if its failing use reject();
In your case you are not managing promise properly that's why it's running asynchronously, better to use following way with the proper returns.
var bar = new Promise((resolve, reject) => {
return result.forEach((row, index, array) => {
var escaped = _.map(row, mysql.escape);
var checkQuery = "SELECT COUNT(*) as found FROM data WHERE field1 = " + escaped[0] + " AND field2 = " + escaped[1] + " AND field3 = " + escaped[2] + " AND field4 = " + escaped[3] + " AND field5 = " + escaped[4] + " AND field6 = " + escaped[5] + " AND field7 = " + escaped[6] + ";";
return conn.query(checkQuery, function (err, res) {
if (err) {
console.log("Error checking row for duplicate");
console.log(checkQuery);
return reject(err);
} else {
if (res[0].found == 0) {
var query = " (";
var escaped = _.map(row, mysql.escape);
var csv = escaped.join(',');
query += csv;
query += ")";
query += row !== _.last(result) ? ',' : ';';
console.log(query);//This will change to inserting the data to the table
return resolve(query)
} else {
console.log("Duplicate found!");
return reject('Duplicate Found');
}
}
});
});
});
bar.then((data) => {
console.log('All done!');
});
In above code I am returning query + resolve/reject so it makes better to run in more synchronised way.
return conn.query(checkQuery, function (err, res) {
Plus, while processing this promise I am handling with .then((data) so I can handle that resolve values here.
bar.then((data) => {
console.log('All done!');
});
Note: If you are rejecting any promise it won't be available in above .then block you'll find this reject in catch block so code will be changed in following way.
bar.then((data) => {
console.log('All done!');
}).catch(err=>{
console.log(err);
});
You can try the following:
(async () => {
await new Promise((resolve, reject) => {
result.forEach((row, index, array) => {
var escaped = _.map(row, mysql.escape);
var checkQuery = "SELECT COUNT(*) as found FROM data WHERE field1 =
" + escaped[0] + " AND field2 = " + escaped[1] + " AND field3 = " +
escaped[2] + " AND field4 = " + escaped[3] + " AND field5 = " + escaped[4] + " AND field6 = " + escaped[5] + " AND field7 = " + escaped[6] + ";";
conn.query(checkQuery, function (err, res) {
if (err) {
console.log("Error checking row for duplicate");
console.log(checkQuery);
process.exit(1);
} else {
if (res[0].found == 0) {
var query = " (";
var escaped = _.map(row, mysql.escape);
var csv = escaped.join(',');
query += csv;
query += ")";
query += row !== _.last(result) ? ',' : ';';
console.log(query);//This will change to inserting the data to the table
}else{
console.log("Duplicate found!");
}
}
});
if (index === array.length -1) resolve();
});
});
console.log('All done!');
})();
You don't even need to call the process.exit(0) because the code will always terminate when the job is done :)
I have a node.js app consisting of a timer calling a user defined function made up of a bunch of functions in node language. The calling script has a timer calling function mybuy() every 10 seconds; mybuy() buys crypto currencies using Binance api according trigger prices contained in a mySQL table (alarms). I would like to start mysell() (not shown , but similar to myBuy()) right after mybuy() has run its course.
How to make mysell() the callback function of mybuy()?
This the calling script:
var fs = require('fs');
var sl = require('alberto/buy');
var loop = 0;
setImmediate(() => {
// start the log
fs.appendFile('./log.txt', "\n Loop-> " + loop + "\n", function (err) {
if (err) { console.log(err); }
})
//execute the function
sl.mybuy(); // USD function; everything happens here.Can take long to finish
var myInt = setInterval(function () {
loop++;
fs.appendFile('./log.txt', "Loop-> " + loop + "\n", function (err) {
if (err) { console.log(err); }
})
//execute every 10 secs
sl.mybuy();
if (loop > 5) { clearInterval(myInt); } // max 6 loops for testing
}, 10000);
});
the UDF id here
exports.mybuy = function () {
var fs = require('fs'); // I keep a log.txt
process.stdout.write("\u001b[2J\u001b[0;0H");// clear screen
aww = (new Date()).toJSON().slice(0, 19).replace(/[-T]/, '-');
aww = aww.replace(/T/, ' ');
console.log(aww, '\n\n'); // practicing with dates
var mysql = require('mysql');
var con = mysql.createConnection({
host: "www.photobangkok.com",
user: "photoban_user",
password: "xxxxxxxx",
database: "photoban_datab"
});
// 'added' is for never processed entries in alarms table.It will change to BOUGHT or SOLD
sql = "SELECT rec, id,coin,buy,amount_b,stat FROM alarms where stat='added' AND buy>0 order by coin";
var cnt = 0; // not used, perhaps an idea to emit an event when cnt reaches the number of rows
con.query(sql, function (err, result) {
if (err) throw err;
str = "";
for (var index in result) {
str = result[index].rec + "-" + result[index].id + "-" + result[index].coin + "-" + result[index].buy + "-" + result[index].amount_b + "-" + result[index].stat;
// set up variables
coin = result[index].coin;
buy = result[index].buy;
rec = result[index].rec;
id = result[index].id;
amount = result[index].amount_b;
console.log('\x1b[36m%s\x1b[0m', str); // set color green. Display str
checkprice(coin, buy, rec, id, amount); //check Binance today price for the coin.The function will execute sometimes
} // end of loop
console.log('\x1b[36m%s\x1b[0m', str); // set color green. Display str
});
//check single coin price using binance api
function checkprice(coin, buy, rec, id, amount) {
const binance = require('node-binance-api')().options({
APIKEY: '<key>',
APISECRET: '<secret>',
useServerTime: true,
test: true //sandbox does not work
});
binance.prices(coin, (error, ticker) => {
act = "Nothing"; // default value
pricenow = ticker[coin]; // note ticker[coin]
if (pricenow < buy) {
show(id, rec, coin, buy, amount, pricenow);// Display sometimes then call book()
} else { console.log(coin, pricenow, buy, act, '\n'); }
});
}
function show(id, rec, coin, buy, amount, pricenow) {
delta = buy - pricenow; // posted trigger - today price
delta = delta.toFixed(8);
console.log('\x1b[31m%s\x1b[0m', coin, buy, amount, id, rec, ">BUY", delta); //display entries from alarms higher that today price
book(id, rec, coin, buy, amount, pricenow);
}
// dummy function to be replaced with a buy api order
function book(id, rec, coin, buy, amount, pricenow) {
const binance = require('node-binance-api')().options({
APIKEY: '<key>',
APISECRET: '<secret>',
useServerTime: true,
test: true //sandbox
});
console.log("Order:buy what??", coin, "amount:", amount, '\n');
/* binance.prices(coin, (error, ticker) => {
console.log("booking",coin, ticker[coin]);
update(id,rec);
}); */
update(id, rec, amount); // update mySql table. Slow but sure
}
function update(id, rec, amount) {
var sql = "UPDATE alarms SET stat = 'BOUGHT' ,today =
CONVERT_TZ(now(), '+00:00', '+7:00') WHERE id = "+id+" AND rec = "+rec;
con.query(sql, function (err, result) {
if (err) throw err;
console.log(result.affectedRows + " record updated");
// keep a log.tx
fs.appendFile('./log.txt', aww + " bought " + id + "-" + rec + "-" + amount + "\n",
function (err) {
if (err) { console.log(err); }
})
});
}
// I could check if all rows are done and raise an event? (how to do it)
} // end
To make mySell as the callback method of myBuy, invoke the myBuy method using the following structure.
myBuy(() => {
// operation of mySell method
});
And your myBuy method should return the callback, after perform its own operation.
exports.myBuy = function(cb) {
// operation of myBuy method
return cb; // return to the mySell method
}
I have an issue with not able to get the affected rows result from the following
During the debug I notice it always crashes at conn.querySync(query.sqlUpdate, params);
Console.log is not showing anything as well.
What did I do wrong here?
CODE
//imports
const format = require('string-format');
const query = require('../db/query');
const message = require('../common/message');
const constant = require('../common/constant');
var ibmdb = require("ibm_db");
require('dotenv').config();
// access the environment variables for this environment
const database = "DATABASE=" + process.env.DATABASE + ";";
const hostname = "HOSTNAME=" + process.env.HOSTNAME + ";";
const uid = "UID=" + process.env.UID + ";";
const pwd = "PWD=" + process.env.PWD + ";";
const dbport = "PORT=" + process.env.DBPORT + ";";
const protocol = "PROTOCOL=" + process.env.PROTOCOL;
const connString = database+hostname+uid+pwd+dbport+protocol;
function updateContact(params) {
ibmdb.open(connString, function(err, conn){
//blocks until the query is completed and all data has been acquired
var rows = conn.querySync(query.sqlUpdate, params);
console.log(rows);
});
}
module.exports.updateContact = updateContact;
I finally understand what the problem is.
The problem lies in me using the querySync function. This function not return affected row counts.
https://github.com/ibmdb/node-ibm_db/blob/master/APIDocumentation.md#querySyncApi
The proper way is to use prepare followed by executeNonQuery.
https://github.com/ibmdb/node-ibm_db/blob/master/APIDocumentation.md#executeNonQueryApi
So from the API, i modify my codes.
...
conn.prepare(query.SQL_UPDATE, function (error, stmt) {
if (err) {
console.log(err);
return conn.closeSync();
}
stmt.executeNonQuery(params, function (err, result) {
if( err ) {
console.log(err);
}
else {
console.log("Affected rows = " + result);
}
//Close the connection
conn.close();
});
});
...
Here is what I'm trying to do:
Start a looping (10x)
Select on sql to return 1 register (select top 1 where 'running' is null)
Sql update 'running' status to 'running'
If the record is null, I access an API and get some data
The result is updated on the initial sql record (set running = 'ok')
End looping (start over)
Thing is, node.js does not wait for start over, it does everything at the same time. That way, 'running' is always null.
var express = require('express');
var app = express();
var c_MyApi = require('./controller/call_MyApi');
var mongo = require('./controller/crud_mongo');
var c_email = require('./controller/api_email_verify');
var c_sql = require('./controller/consulta_sql');
var MyLink = '',
id = 0;
for( var i = 0 ; i < 10 ; i++){
c_sql.busca().then(function(res) {
MyLink = res[0].MyLink;
id = res[0].id;
c_sql.marca(id).then(
c_MyApi.busca(MyLink, function(a) {
if (a == 0) {
c_sql.atualiza(id, 'sem_email', 's/e');
}
if (a == 1) {
c_sql.atualiza(id, 'link_errado', 'l/e');
} else {
for (var i = 0; i < a.length; i++) {
var email = a[i].address;
c_email.busca(email, function(e_valid) {
c_sql.atualiza(id, email, e_valid)
})
}
}
})
)
})
}
}
//consulta_sql.js
var sql = require("seriate");
var search = function() {
var exec = 'select top 1 MyLink, id from sys_robo_fila where done is null';
sql.setDefaultConfig(config);
return sql.execute({
query: exec
});
}
var usado = function(id) {
var exec = "update sys_robo_fila set done = 'r' where id = " + id + "";
sql.setDefaultConfig(config);
return sql.execute({
query: exec
});
}
var update = function(id, email, valid) {
var exec = "update sys_robo_fila set email = '" + email + "' , validacao = '" + valid + "' , done = 'ok' where id = " + id + "";
sql.setDefaultConfig(config);
return sql.execute({
query: exec
});
}
module.exports = {
busca: search,
atualiza: update,
marca: usado
}
Any sugestions?
The call to c_sql.busca() is returning a Promise immediately and then continuing on to the next loop before then() is called, which is why they seem to run at the same time (they are actually just running very quickly, but before the Promise is resolved.
If you want this to run synchronously, one loop at a time, I would recommend using a recursive function to not start the loop again until the Promises have resolved.
let count = 0;
function doSomething() {
// this returns immediately, before .then() executes
return c_sql.busca()
.then(() => {
// do some more stuff after c_sql.busca() resolves
return c_sql.busca();
})
.then(() => {
// increment your count
count += 1
if (count<10) {
// less than 10 runs call the function again to start over
return doSomething();
}
});
}
This article might be helpful in understanding Promises: https://medium.com/#bluepnume/learn-about-promises-before-you-start-using-async-await-eb148164a9c8
I recently stating coding in node.js and might be a very simple question.
Trying to write a XML parser/validator to validate xml schema and values against values/ xpath stored in an excel sheet.
Now once the validation function is complete I want to call a printResult function to print final result. However if I try to call the function immediately after the first function .. its printing variables initial values and if called within the first which is iterating though the number of xpaths present in excel sheet and printing result with increments.
var mocha = require('mocha');
var assert = require('chai').assert;
var fs = require('fs');
var parseString = require('xml2js').parseString;
var xpath = require('xpath');
var dom = require('xmldom').DOMParser;
var XLSX = require('xlsx');
var Excel = require("exceljs");
var should = require('chai').should();
var HashMap = require('hashmap');
var colors = require('colors');
require('/xmlValidator/dbConnect.js');
var map = new HashMap();
var elementMap = new HashMap();
var resultValue;
//console.log('hello'.green);
map.set("PASS", 0);
map.set("FAIL", 0);
map.set("INVALID_PATH", 0);
function computeResult(elementPath, result) {
var pass = map.get("PASS");
var fail = map.get("FAIL");
var invalidPath = map.get("INVALID_PATH");
elementMap.set(elementPath, result);
if (result == "PASS") {
pass++;
map.set("PASS", pass);
} else if (result == "FAIL") {
fail++;
map.set("FAIL", fail);
} else {
invalidPath++;
map.set("INVALID_PATH", invalidPath)
}
printResult();
}
function printResult() {
var pass = map.get("PASS");
var fail = map.get("FAIL");
var invalidPath = map.get("INVALID_PATH");
console.log(("PASS Count :" + pass).green);
console.log(("FAIL Count :" + fail).red);
console.log(("Inavlid Path :" + invalidPath).yellow);
elementMap.forEach(function(value, key) {
if (value == "INVALID_PATH")
console.log((key + ":" + value).yellow);
else if (value == "FAIL")
console.log((key + ":" + value).red);
else
console.log(key + ":" + value);
});
}
var workbook = new Excel.Workbook();
workbook.xlsx.readFile('utils/' + process.argv[2])
.then(function() {
var worksheet = workbook.getWorksheet(1);
worksheet.eachRow(function(row, rowNumber) {
//console.log(rowNumber);
var row = worksheet.getRow(rowNumber);
var dataPath1 = row.getCell("A").value;
var dataPath2 = row.getCell("B").value;
var dataPath = dataPath1 + dataPath2;
//console.log(dataPath);
var dataValue = row.getCell("D").value;
var flag = row.getCell("E").value;
//console.log(flag)
//console.log(dataValue);
if (!flag)
validate(dataPath, dataValue, rowNumber);
//else console.log("NOT EXECUTED" + rowNumber)
});
})
function validate(dataPath, dataValue, rowNumber) {
var fail = 0;
fs.readFile('utils/' + process.argv[3], 'utf8', function(err, data) {
if (err) {
console.log("ERROR ERROR ERROR ERROR ");
return console.log(err);
}
var doc = new dom().parseFromString(data);
var subId = String(xpath.select1(dataPath, doc));
if (subId == "undefined") {
/*console.log('undefined caught');
console.log("row number :" + rowNumber);*/
var resultValue = "INVALID_PATH";
computeResult(dataPath, resultValue);
} else {
var subId = xpath.select1(dataPath, doc);
var value = subId.lastChild.data;
/*console.log("row number :" + rowNumber);
console.log("actual value: " + value);
console.log("expected value:" + dataValue );*/
if (dataValue == null) {
assert.notEqual(value, dataValue, "value not found");
resultValue = "PASS";
computeResult(dataPath, resultValue);
} else {
if (value == dataValue)
resultValue = "PASS";
else resultValue = "FAIL";
computeResult(dataPath, resultValue);
}
}
});
}
In the code above i want to call printResult() function after validate function is completely executed (workbook.xlsx.readFile)
Can some one please help me out how to use done () function or make sync call ?
If fs.readFileAsync('utils/' + process.argv[3], 'utf8') can be executed once, then validate() will be completely synchronous and calling printResult() after the verification loop will be trivial.
In the main routine, you can simply aggregate two promises ...
var promise1 = workbook.xlsx.readFile();
var promise2 = fs.readFileAsync(); // requires `fs` to be promisified.
... before embarking on the verification loop.
Promise.all([promise1, promise2]).spread(/* verify here */);
Also a whole bunch of tidying can be considered, in particular :
establishing "PASS", "FAIL" and "INVALID_PATH" as constants to avoid lots of repetitive string creation,
using js plain objects in lieu of hashmap,
building map from elementMap inside the print function.
having validate() return its result and building elementMap in the main routine
Here's the whole thing, about as tight as I can get it. I may have made a few assumptions but hopefully not too many bad ones ...
var mocha = require('mocha');
var assert = require('chai').assert;
var Promise = require("bluebird");
var fs = Promise.promisifyAll(require("fs")); // allow Bluebird to take the pain out of promisification.
var parseString = require('xml2js').parseString;
var xpath = require('xpath');
var dom = require('xmldom').DOMParser;
var XLSX = require('xlsx');
var Excel = require("exceljs");
var should = require('chai').should();
// var HashMap = require('hashmap');
var colors = require('colors');
require('/xmlValidator/dbConnect.js');
const PASS = "PASS";
const FAIL = "FAIL";
const INVALID_PATH = "INVALID_PATH";
function printResult(elementMap) {
var key, result,
map = { PASS: 0, FAIL: 0, INVALID_PATH: 0 },
colorNames = { PASS: 'black', FAIL: 'red', INVALID_PATH: 'yellow' };
for(key in elementMap) {
result = elementMap[key];
map[(result === PASS || result === FAIL) ? result : INVALID_PATH] += 1;
console.log((key + ": " + result)[colorNames[result] || 'black']); // presumably colors can be applied with associative syntax? If so, then the code can be very concise.
}
console.log(("PASS Count: " + map.PASS)[colorNames.PASS]);
console.log(("FAIL Count: " + map.FAIL)[colorNames.FAIL]);
console.log(("Inavlid Path: " + map.INVALID_PATH)[colorNames.INVALID_PATH]);
}
function validate(doc, dataPath, dataValue) {
var subId = xpath.select1(dataPath, doc),
value = subId.lastChild.data,
result;
if (String(subId) == "undefined") {
result = INVALID_PATH;
} else {
if (dataValue === null) {
assert.notEqual(value, dataValue, "value not found"); // not too sure what this does
result = PASS;
} else {
result = (value === dataValue) ? PASS : FAIL;
}
}
return result;
}
//Main routine
var workbook = new Excel.Workbook();
var promise1 = workbook.xlsx.readFile('utils/' + process.argv[2]); // from the question, workbook.xlsx.readFile() appears to return a promise.
var promise2 = fs.readFileAsync('utils/' + process.argv[3], 'utf8');
Promise.all([promise1, promise2]).spread(function(data2, data3) {
var worksheet = workbook.getWorksheet(1),
doc = new dom().parseFromString(data3),
elementMap = {};
worksheet.eachRow(function(row, rowNumber) {
// var row = worksheet.getRow(rowNumber); // row is already a formal variable ???
var dataPath, dataValue;
if (!row.getCell('E').value)
dataPath = row.getCell('A').value + row.getCell('B').value;
dataValue = row.getCell('D').value;
elementMap[dataPath] = validate(doc, dataPath, dataValue);
});
printResult(elementMap);
});
Untested so may not run but at least you can raid the code for ideas.