Synchronous function calls for nodejs mongodb driver - javascript

I have an open source project that deals with mongodb database. I am trying to make a function that queries the database to check if entry exists.
The problem is when if_exists() returning true or false it returns undefined since the mongodb driver function is asynchronous. The file is Query.js and I have tried the solution here to workaround the problem What is the right way to make a synchronous MongoDB query in Node.js? but still I get an undefined result with the get method.
What is the best way to make this work?
The output from the unit tests is as the following:
running unit tests...
add query test
exists tests:
get: undefined
{}
should be true: undefined
get: undefined
{}
should be false:undefined
Captains Logs listening on port 3000
Captains_Logs v0.5.0-21
[ { name: 'rhcp', _id: 50cbdcbe9c3cf97203000002 } ]
[ { name: 'os', _id: 50cbdcbe9c3cf97203000001 } ]
You can browse the whole codes at WeaponXI/cplog
Or for a quick look the query.js code is:
var DB = require('../../lib/db.js').DB;
function methods() {
//query object
var Q = {};
//will act as our private variables to workaround asynchronous functions.
//will delete non-required ones when done -- we don't have to, but just for continuity.
exports.privates = {};
//add tag to collection
Q.add = function(tag) {
if (typeof tag === "string") {
//maybe we are adding a tag by name
var obj = {
name: tag
};
} else if (typeof tag === "object" && tag.name) {
//maybe the tag object was specified, and tag's name was provided
var obj = tag;
}
require('mongodb').connect(DB.mongo_url, function(err, db) {
db.collection('tags', function(err, coll) {
coll.insert(obj, {
safe: true
}, function(err, result) {
console.log(result);
});
});
});
}
var callback = {
_set: function(key, val) {
exports.privates[key] = val;
//console.log(JSON.stringify(privates));
},
_get: function(key) {
console.log("get: "+exports.privates.key);
console.log(JSON.stringify(exports.privates));
return exports.privates[key];
},
_unset: function(key) {
delete privates[key];
}
}
var if_exists = function(query, where, callback) {
require('mongodb').connect(DB.mongo_url, function(err, db) {
db.collection(where, function(err, coll) {
coll.findOne(query, function(e, r) {
//console.log(r);
if (r === null) {
callback._set("does_exist", false);
} else {
callback._set("does_exist", true);
}
});
});
});
var result = callback._get("does_exist");
// delete privates.does_exist;
return result;
}
Q.if_exists = function(query, where) {
if_exists(query, where, callback);
}
return Q;
}
var query = exports.query = methods();
function unit_test_add() {
console.log("add query test");
query.add("os");
query.add({
name: "rhcp"
});
}
function unit_test_if_exists() {
console.log("exists tests:");
console.log("should be true: " + query.if_exists({
name: "os"
}, "tags"));
console.log("should be false:" + query.if_exists({
name: "ossuruk"
}, "tags"));
}
function unit_tests() {
console.log("running unit tests...");
unit_test_add();
unit_test_if_exists();
}
unit_tests();
Solution:
Query.js Query.test.js Gists
Thanks JohnnyHK!

You cannot use an asynchronous result as the return value from a function. It's that simple. You have to deliver the asynchronous result to the caller via a callback that is provided as a parameter to the function (or use futures/promises and effectively defer that step, but that's more involved).
if_exists should look like this instead:
var if_exists = function(query, where, callback) {
require('mongodb').connect(DB.mongo_url, function(err, db) {
db.collection(where, function(err, coll) {
coll.findOne(query, function(e, r) {
//console.log(r);
if (r === null) {
callback(e, false);
} else {
callback(e, true);
}
// You should either close db here or connect during start up
// and leave it open.
db.close();
});
});
});
}

Related

How to read multiple json file using fs and bulk request

I'm using elasticsearch search engine with my react app, I was reading one file at the backend as you see in the code and it work perfectly, but now I want to read three different JSON files to three different indexes using the "fs" package and bulk request, can you please help me?
the code:
// Start reading the json file
fs.readFile("DocRes.json", { encoding: "utf-8" }, function (err, data) {
if (err) {
throw err;
}
// Build up a giant bulk request for elasticsearch.
bulk_request = data.split("\n").reduce(function (bulk_request, line) {
var obj, ncar;
try {
obj = JSON.parse(line);
} catch (e) {
console.log("Done reading 1");
return bulk_request;
}
// Rework the data slightly
ncar = {
id: obj.id,
name: obj.name,
summary: obj.summary,
image: obj.image,
approvetool: obj.approvetool,
num: obj.num,
date: obj.date,
};
bulk_request.push({
index: { _index: "ncar_index", _type: "ncar", _id: ncar.id },
});
bulk_request.push(ncar);
return bulk_request;
}, []);
// A little voodoo to simulate synchronous insert
var busy = false;
var callback = function (err, resp) {
if (err) {
console.log(err);
}
busy = false;
};
// Recursively whittle away at bulk_request, 1000 at a time.
var perhaps_insert = function () {
if (!busy) {
busy = true;
client.bulk(
{
body: bulk_request.slice(0, 1000),
},
callback
);
bulk_request = bulk_request.slice(1000);
console.log(bulk_request.length);
}
if (bulk_request.length > 0) {
setTimeout(perhaps_insert, 100);
} else {
console.log("Inserted all records.");
}
};
perhaps_insert();
});
You can create multiple promises for each file read and feed it to the elastic search bulk_request.
const fsPromises = require('fs').promises,
files = ['filename1', 'filename1'],
response = [];
const fetchFile = async (filename) => {
return new Promise((resolve, reject) => {
const path = path.join(__dirname, filename);
try {
const data = await fsPromises.readFile(path)); // make sure path is correct
resolve(data);
} catch (e) {
reject(e)
}
});
files.forEach((fileName) => results.push(fetchFile()));
Promise.all(results).then(data => console.log(data)).catch(e => console.log(e));
}
Once you get data from all the promises pass it to the elastic search.

Problems getting slot in AWS Lambda function

I am trying to build a simple Alexa Skill. Now, I want to access some of my slot values. This is my code. When I uncomment even the line that I define Alexa in, my skill will not work. Also, if I only uncomment the line defining var text, I still get "there was a problem with the skills response". Const gives the same output. I am using custom slots called recipe. How can I access the slots in my lambda function? Thanks.
const breakfast = {
"bacon and eggs":["bacon","egg"],
"buttered toast":["bread", "butter"]
};
const lunch = {
"ham sandwich":["ham","cheese"]
};
const dinner = { "Steak and eggs": ['steak','eggs']};
//const Alexa = require('ask-sdk-core');
exports.handler = (event, context, callback) => {
try {
if (event.request.type === 'LaunchRequest') {
callback(null, buildResponse('Hello from Lambda'));
} else if (event.request.type === 'IntentRequest') {
const intentName = event.request.intent.name;
if (intentName === 'breakfast') {
callback(null, buildResponse(Object.keys(breakfast)+"") );
}
else if (intentName === 'lunch') {
callback(null, buildResponse(Object.keys(lunch)+"") );
}
else if (intentName === 'dinner') {
callback(null, buildResponse(Object.keys(dinner)+"") );
}
else if (intentName ==='requestRecipe'){
//var text = this.event.request.intent.slots.recipe.value;
//const meal = Alexa.getSlotValue(intentName, "meal")
callback(null, buildResponse("Recipe requested") );
}
else {
callback(null, buildResponse("Sorry, i don't understand"));
}
} else if (event.request.type === 'SessionEndedRequest') {
callback(null, buildResponse('Session Ended'));
}
} catch (e) {
context.fail(`Exception: ${e}`);
}
};
function buildResponse(response) {
return {
version: '1.0',
response: {
outputSpeech: {
type: 'PlainText',
text: response,
},
shouldEndSession: false,
},
sessionAttributes: {},
};
}
for a bit of context: my lambda has the endpoint of what my alexa hosted skill was, and the alexa skill has the endpoint of the lambda. when I say const gives the same output, i mean instead of using var, when I use const, it does the same thing. The JSON file that i get as a reply is empty brackets.
I found the issue behind my problem. Instead of using
//var text = this.event.request.intent.slots.recipe.value;
i simply did var text = event.request.intent.slots.recipe.value; I am now able to use text in building a response or something like that.

Ensure for loop waits until moving onto next item in JavaScript

Firstly I appreciate that there are many answers out there explaining this topic but I just can't understand it at the moment.
I want to loop through a JavaScript object I have created and then perform various actions like making a request to an API and then storing some data in Redis.
This is what I have so far
const params = { "handle1": { "screen_name": "handle1", "hash_tag": "#hashtag1"},
"handle2": { "screen_name": "handle2", "hash_tag": "#hashtag2"} }
for (const k of Object.keys(params)) {
console.log("Searching for " + params[k]['screen_name'])
client.get('statuses/user_timeline', { screen_name: params[k]['screen_name']})
.then(function (tweets) {
for (const key of Object.keys(tweets)) {
const val = tweets[key]['text'];
if(val.includes(params[k]['hash_tag'])) {
console.log("Found")
r_client.hset(params[k]['screen_name'], 'tweet_id', tweets[key]['id'], 'tweet_text', tweets[key]['text'], function (err, res) {
console.log(res)
});
r_client.hgetall(params[k]['screen_name'], function(err, object) {
console.log(object);
});
}
}
r_client.quit();
})
.catch(function (error) {
throw error;
});
}
When I run this the output is as follows
Searching for handle1
Searching for handle2
Found
0
{ tweet_id: '123456789',
tweet_text: 'text found in tweet' }
Found
undefined
undefined
So straight away I have a problem in that the first loop hasn't event finished and it's moved onto the second loop.
I would like to run this in sequential order (if that's the best way), but more importantly I was hoping someone could break down my code and explain how I should be approaching this to have it run correctly.
const tweets = await client.get(...) should do the trick.
Under the condition that .get(), .hset() and hgetall() return Promises this should pause the execution until all functions have resolved:
const params = {
"handle1": {
"screen_name": "handle1",
"hash_tag": "#hashtag1"
},
"handle2": {
"screen_name": "handle2",
"hash_tag": "#hashtag2"
}
}
async function search(params) {
for (const k in params) { //for..in goes through the keys
console.log("Searching for " + params[k]['screen_name'])
const tweets = await client.get('statuses/user_timeline', {
screen_name: params[k]['screen_name']
});
for (const key in tweets) { //same loop
const val = tweets[key]['text'];
if (val.includes(params[k]['hash_tag'])) {
console.log("Found")
await r_client.hset(params[k]['screen_name'], 'tweet_id', tweets[key]['id'], 'tweet_text', tweets[key]['text'], (err, res) => {
console.log(res)
});
await r_client.hgetall(params[k]['screen_name'], (err, object) => {
console.log(object);
});
}
}
r_client.quit();
}
}
search(params);

How to perserve array value after multiple async callback in node js?

I am trying to execute multiple callback and at the same time storing value in array , but at the end array return empty.
Here is my code :
var sheetData = [];
async.forEachSeries(req.body.data, function (data, cb) {
sheet.find({accountid: req.body.id}, function (err, doc) {
if (doc == '') {// get the next worksheet and save it
var sheet = new sheet({
accountid: req.body.id,
sheetid: data.sheetid
});
var jsonData = {};
jsonData.sheetid = data.sheetid;
sheet.save(function (err, doc) {
if (!err) {
sheetData.push(jsonData); // trying to push in array , success
console.log("-----sheet data---- : ", sheetData);// data available here
}
});
}
});
cb();
}, function () {
console.log("-----sheet data---- : ", sheetData);// empty array
});
Where I am doing wrong? Can anyone suggest me ?
Or, If any other alternative in nodejs.
Thanks
The callback is being called early. Try following:
var sheetData = [];
async.forEachSeries(req.body.data, function (data, cb) {
sheet.find({accountid: req.body.id}, function (err, doc) {
if (!doc) {
return cb (); //sheet exists, call back early
}
// get the next worksheet and save it
var sheet = new sheet({
accountid: req.body.id,
sheetid: data.sheetid
});
var jsonData = {};
jsonData.sheetid = data.sheetid;
sheet.save(function (err, doc) {
if (!err) {
sheetData.push(jsonData); // trying to push in array , success
console.log("-----sheet data---- : ", sheetData);// data available here
cb (); // all done, now we can call back
}
});
});
}, function () {
console.log("-----sheet data---- : ", sheetData);// lots of sheets
});

Comparing 2 attributes using the function "some"

var e = require("./myApp.js");
var myServer = e.CreateServer(1337);
myServer.Register("/", "GET", function (req, res) { res.end("J") });
myServer.Register("/", "GET", function (req, res) { res.end("Ja") });
myServer.Start();
This is my "Wrapper":
module.exports = (function () {
function _createServer(port) {
var routingTable = [];
var port = port;
var server = require('http').createServer();
function _start() {
server.listen(port);
console.log("Server was started");
};
function RegisterRecord(url, method, fnc) {
this.url = url;
this.method = method;
this.fnc = fnc;
};
function _register(newUrl, newMethod, newFnc) {
if (_checkInput(newUrl, newMethod))
console.log("Register failed! Record with same URL and Method already exist");
else {
routingTable.push(new RegisterRecord(newUrl, newMethod, newFnc));
console.log("Register success!");
}
};
function _checkInput(newUrl, newMethod) {
return routingTable.some(function fnc(record) { record.url == newUrl && record.method == newMethod });
};
return {
Start: _start,
Register: _register,
ShutDown: _shutDown
};
};
return { CreateServer: _createServer };
})();
So the most important functions are "_register" and "checkInput".
My aim is that the same URL and Method are only allowed on time in the array routingTable. So when I execute the programm, the Command Promp prints two times Register success. But "/" and "GET" should only be allowed one time.
How can I compare the URL and method so that they can be unique?
PS: The "Wrapper" is in the JS File "./MyApp.js"
You need filter:
function _checkInput(newUrl, newMethod) {
return routingTable
.filter( function(el) {
return el.url === newUrl && el.method === newMethod;
})
.length > 0;
};
Upd. Of course, you can use the some - you just forgot to return a value from it:
function _checkInput(newUrl, newMethod) {
return routingTable
.some( function(el) {
// Need return
return el.url === newUrl && el.method === newMethod;
})
};

Categories

Resources