How to make a webservice call synchronous in meteor - node.js

I am trying to call a simple weather service synchronously in meteor. I am not even able to make the client creating synchronous. In the code below I expect user.testData to contain "test data" but it contains nothing.
if (Meteor.isClient) {
Template.confirmWeather.onRendered(function(){
var validator = $('.confirmWeatherAndGoToMessage').validate({
submitHandler: function(event){
Meteor.bindEnvironment(Meteor.call('testWeather',Meteor.bindEnvironment(function(error,result)
{
if (error)
{
console.log(error.message)
}
else
{
var userId = Meteor.userId();
var user = Meteor.users.findOne({_id: userId});
if (user.testData)
{
console.log(user.testData);
}
}
})));
}
});
});
}
if (Meteor.isServer) {
Meteor.methods({
testWeather: function () {
var soap = Npm.require('soap');
var options = {
ignoredNamespaces: {
namespaces: [],
override: true
}
}
var url = 'http://www.webservicex.com/globalweather.asmx?WSDL';
Meteor.wrapAsync(soap.createClient(url, options,Meteor.bindEnvironment(function(err, client) {
if (err)
{
console.log("CREATE ERROR:");
console.log(err);
}
else
{
Meteor.wrapAsync(Meteor.call("insertIntoTestData","test data",function(err,res)
{
if (err)
{
throw new Meteor.Error("server-error",error.message);
}
else
{
console.log('DATA: ');
}
}));
}
})));
},
insertIntoTestData: function(data) {
var userId = Meteor.userId();
var user = Meteor.users.findOne({_id: userId});
if (user)
{
return resultId = Meteor.users.update({_id:userId},{$set:{testData:data}});
}
}
});
}

If you can't make that approach work maybe i can offer alternative. When i needed sync calls with request library i used different approach by using future library from fibers/future nodejs. After the request i just put "future.wait()" and inside async code callback "future.return(value)". Execution would then wait until request was finished and returned a value.
This link here nicely explains use of that approach also: https://themeteorchef.com/tutorials/synchronous-methods
Best regards,
Dino

Related

NodeJS - Nested Promise inside a for loop

I am trying to do a call which retrieves a list of categories. Inside this call I want to loop through the categories and retrieve the items for each category and return them all together. My call retrieves the categories perfectly before I added the loop to retrieve the items.
To double check my call to another controller works, I added a proof of concept block of code which you can see below is commented out. So I know it isn't the call to an external class.
Here is my code:
'use strict';
var mongoose = require('mongoose'),
MenuCategory = mongoose.model('MenuCategory');
module.exports = function(menuItemController) {
var mod = {
listEntireMenu(req, res) {
return new Promise(function(resolve, reject) {
var entireMenu = [];
MenuCategory.find({}, function(err, menuCategories) {
if (err) {
return reject(err)
} else {
//---------------------------
// PROOF OF CONCEPT THAT CALL TO OTHER CONTROLLER WORKS
//---------------------------
//
// var categoryWithItems = menuCategories[0].toObject();
// req.body.menuCategoryID = categoryWithItems._id;
// menuItemController.listAllMenuItemsByCategory(req, res).then((menuItems) => {
// if(menuItems)
// {
// return resolve(menuItems);
// }
// else
// {
// return { success: false }
// }
// });
//-----------------------------
for (var i = 0; i < menuCategories.length; i++) {
var categoryWithItems = menuCategories[i].toObject();
var subItems = [];
req.body.menuCategoryID = categoryWithItems._id;
menuItemController.listAllMenuItemsByCategory(req, res).then((menuItems) => {
if(menuItems)
{
subItems = menuItems;
}
else
{
return { success: false }
}
});
categoryWithItems.tester = { "itemsList" : subItems };
entireMenu.push(categoryWithItems);
}
return resolve(entireMenu)
}
});
}).then((menuCategories) => {
if(menuCategories)
{
return menuCategories
}
else
{
return { success: false }
}
});
},
}
return mod;
};
What I actually get returned is this :
[
{
"_id": "5ed16fxxxxxxxx95676e37",
"locationID": "5ed16xxxxxxxx7295676e36",
"menuCategoryName": "Category One",
"Created_date": "2020-05-29T20:26:34.991Z",
"__v": 0,
"tester": {
"itemsList": []
}
},
{
"_id": "5ed170xxxxxx95676e38",
"locationID": "5ed16xxxxxxxx7295676e36",
"menuCategoryName": "Category Two",
"Created_date": "2020-05-29T20:26:48.799Z",
"__v": 0,
"tester": {
"itemsList": []
}
}
]
Here is the call from the route.js :
app.get('/api/listEntireMenu', (req, res) => {
menuCategoryController.listEntireMenu(req, res).then(menuCategories => res.json(menuCategories));
})
It never writes the subItems into the object. Is this an async issue or something else? I am not sure how to solve this.
Thanks in advance.
i believe the reason the result of your call to resolve is being returned before the requests are able to complete...for this you need to wait until all the promises or requests have finished properly and returned.
There are two ways you can do this: you could either run them one by one and wait for each one to finish first or run them all concurrently until all of them are done.
Ofcourse the fastest way to do it would be to run them all concurrently so lets go for that way:
so to start, let us not use the for loop and instead remap the iterable array menuCategories to promises of the request, we will use your proof of concept code to make the array of promises
//...
Promise.all(
menuCategories.map((category) => {
let category_with_items = category.toObject();
req.body.menuCategoryID = category_with_items._id;
// here we need to return this since its the promise we are remapping to
return menuItemController.listAllMenuItemsByCategory(req, res)
.then((menuitems) => {
if(menuItems) {
return menuitems;
}
throw 'No menu items found'
});
});
)
// each promise will return menuitems so we have to wait for all the promises to complete
// then with the results of each promise we push the items into the entire menu
.then((itemslist) => {
itemslist.forEach((items) => entireMenu.push(items));
return entireMenu;
})
// lastly we need to handle any errors from the promises
.catch((error) => { success: false });
//...
So now we have...
listEntireMenu(req, res) {
return MenuCategory.find({}, function(err, menuCategories) {
if (err) {
throw err
} else {
entireMenu = [];
return /* the promise all call from above will go right here */;
}
}
I hope it works out, thanks...

MongoDB in NodeJS Class Structure

Is there a way to use MongoDB in a class structure in NodeJS?
I understand you can do CRUD operations on the DB within the connection method like
mongo.connect(url, function(err, client){//do some CRUD operation});
but I was wondering if there was a way to open the connection to the DB, have access to it across the class, then close it when you are done working with the class.
For example:
class MyClass {
constructor(databaseURL) {
this.url = databaseURL;
}
async init() {
//make connection to database
}
async complete_TaskA_onDB() {
//...
}
async complete_TaskB_onDB() {
//...
}
async close_connection() {
//close connection to database
}
}
Edit:
I just came across more information in the Node.JS Mongo docs. Maybe something along the lines of this would work?
//constructor()
this.db = new MongoClient(new Server(dbHost, dbPort));
//init()
this.db.open();
//taskA()
this.db.collection(...).update(...);
//close_connection()
this.db.close();
You can create a class of which will act as a wrapper on any core lib, doing so will give you below advantages:
Wrapping any core module with your own service will allow you to:
Create a reusable service that you could use on multiple components in your app.
Normalize the module's API, and add more methods your app needs and the module doesn’t provide.
Easily replace the DB module you chose with another one (if needed).
I have created that service which I use it in my projects for MongoDB:
var mongoClient = require("mongodb").MongoClient,
db;
function isObject(obj) {
return Object.keys(obj).length > 0 && obj.constructor === Object;
}
class mongoDbClient {
async connect(conn, onSuccess, onFailure){
try {
var connection = await mongoClient.connect(conn.url, { useNewUrlParser: true });
this.db = connection.db(conn.dbName);
logger.info("MongoClient Connection successfull.");
onSuccess();
}
catch(ex) {
logger.error("Error caught,", ex);
onFailure(ex);
}
}
async getNextSequence(coll) {
return await this.db.collection("counters").findOneAndUpdate({
_id: coll
},
{$inc: {seq: 1}},
{projections: {seq: 1},
upsert: true,
returnOriginal: false
}
);
}
async insertDocumentWithIndex(coll, doc) {
try {
if(!isObject(doc)){
throw Error("mongoClient.insertDocumentWithIndex: document is not an object");
return;
}
var index = await this.getNextSequence(coll);
doc.idx = index.value.seq;
return await this.db.collection(coll).insertOne(doc);
}
catch(e) {
logger.error("mongoClient.insertDocumentWithIndex: Error caught,", e);
return Promise.reject(e);
}
}
async findDocFieldsByFilter(coll, query, projection, lmt) {
if(!query){
throw Error("mongoClient.findDocFieldsByFilter: query is not an object");
}
return await this.db.collection(coll).find(query, {
projection: projection || {},
limit: lmt || 0
}).toArray();
}
async findDocByAggregation(coll, query) {
if(!query.length){
throw Error("mongoClient.findDocByAggregation: query is not an object");
}
return this.db.collection(coll).aggregate(query).toArray();
}
async getDocumentCountByQuery(coll, query) {
return this.db.collection(coll).estimatedDocumentCount(query || {})
}
async findOneAndUpdate(coll, query, values, option) {
if(!(isObject(values) && isObject(query))){
throw Error("mongoClient.UpdateDocument: values and query should be an object");
}
return this.db.collection(coll).findOneAndUpdate(query, {$set : values}, option || {})
}
async modifyOneDocument(coll, query, values, option) {
if(!(isObject(values) && isObject(query))){
throw Error("mongoClient.ModifyOneDocument: values, query and option should be an object");
}
return await this.db.collection(coll).updateOne(query, values, option || {})
}
async close() {
return await this.db.close();
}
}
module.exports = {
mongoDbClient: mongoDbClient
}
For my complete lib access you can refer here
Yes, you can do all of this inside a class, but you cannot set a member variable like db after the constructor has been set. You can make it a global variable, but you cannot set the variable.
const MongoClient = require('mongodb').MongoClient;
var database; //global
class DB {
constructor(url, dbName) {
this.url = url;
this.dbName = dbName;
}
connect() {
console.log('connecting to database ' + this.dbName + ' with URL ' + this.url);
return new Promise((resolve, reject) => {
MongoClient.connect(this.url, (err, client) => {
if (err) {
reject(err);
} else {
database = client.db(this.dbName);
resolve(client.db(this.dbName));
}
});
})
}
}

Unable to access get method from request package in the function node

I have added request module and made changes in main settings.js as per the documentation :
// Anything in this hash is globally available to all functions.
// It is accessed as context.global.
// eg:
// functionGlobalContext: { os:require('os') }
// can be accessed in a function block as:
// context.global.os
functionGlobalContext: {
requestModule:require('request').defaults({jar: true})
// os:require('os'),
// octalbonescript:require('octalbonescript'),
// jfive:require("johnny-five"),
// j5board:require("johnny-five").Board({repl:false})
},
This is the code in one of my function nodes :
var request = context.global.requestModule;
request.get('http://192.168.0.44:3000' + '/api/stats',
function (err, data) {
if (err) {
console.log("could not process data");
}
else {
var body = JSON.parse(data.body);
msg.payload = body.deviceCount;
}
});
return msg;
My node red version is (0.16.2)
That won't work because you return msg before the async call back in the request object is called. The following should work:
var request = context.global.requestModule;
request.get('http://192.168.0.44:3000' + '/api/stats',
function (err, data) {
if (err) {
console.log("could not process data");
}
else {
var body = JSON.parse(data.body);
msg.payload = body.deviceCount;
node.send(msg)
}
});

I am having issues with exporting in node

I am trying to make an api endpoint for data coming from dynamoDB. I believe that I have everything connected but when I run postman to check the api (api/db) it doesn't recognize the functions from the db.js in the db.js (for routes). I have run a test on api/test and am getting the information back. Here is the code from both files:
1. This scans the database and I'm trying to export it to another file.
var AWS = require('aws-sdk');
var params = {
TableName : "iotbuttonsn",
//KeyConditionExpression: "serialNumber =:serialNumber",
//ExpressionAttributeValues: {
// ":serialNumber":"*"
//},
ScanIndexForward: false,
Limit: 3,
Select: 'ALL_ATTRIBUTES'
};
AWS.config.update({
region: "us-east-1",
endpoint: "https://dynamodb.us-east-1.amazonaws.com"
});
var docClient = new AWS.DynamoDB.DocumentClient();
var getDatabase = (function(){
return {
scanDB: function(){
docClient.scan(params, onScan);
var onScan = function(err, data){
if (err) {
console.log(err.message);
} else {
console.log('scan success');
len = data.Items.length;
for (n=0; n<len; n++) {
clickTypes[n] = data.Items[n].payload.clickType;
serialNums[n] = data.Items[n].serialNumber;
}
}
};
},
clickTypes: [],
serialNums: []
};
})();
module.exports = getDatabase;
2. This is where I'm trying to input but db.scanDB() isn't working:
var router = require('express').Router();
var db = require('../routes/db.js');
router.get('/', function(req, res){
db.scanDB();
buttons =
[
iot_buttonOne = {
serialNum: db.serialNum[0],
clickType: db.clickTypes[0]
},
iot_buttonTwo = {
serialNum: db.serialNum[1],
clickType: db.clickTypes[1]
}
]
.then(
function scanSuccess(data){
res.json(data);
},
function scanError(err){
res.send(500, err.message);
}
);
});
module.exports = router;
Change your db.scan() function to properly return an asynchronous result:
// db.js
module.exports = {
scanDB: function(cb){
docClient.scan(params, function(err, data) {
var clickTypes = [], serialNums = [];
if (err) {
console.log(err.message);
cb(err);
} else {
console.log('scan success');
len = data.Items.length;
for (n=0; n<len; n++) {
clickTypes[n] = data.Items[n].payload.clickType;
serialNums[n] = data.Items[n].serialNumber;
}
cb(null, {clickTypes, serialNums});
}
});
}
};
Then, when you use it:
var db = require('../routes/db.js');
db.scanDB(function(err, data) {
if (!err) {
// data.clickTypes
// data.serialNums
} else {
// process error
}
});
It really does not good to put the scanDB result on the DB object the way you were doing because there was no way for the caller to know when the asynchronous operation was done. So, since you have to provide some notification for the caller when the async operation is done (either via callback or promise), you may as well just pass the results there too.
Also, the .then() handler in your router.get(...) handler does not belong there. I don't know why it's there at all as there are no promises involved in the code you show. Perhaps a cut/paste error when creating the question?
Note, I removed the IIFE from your getDatabase() definition since there was no benefit to it other than a little more complicated code.

AngularJs $resource save throwing ERR_CONTENT_LENGTH_MISMATCH

Using AngularJs $resource for updating the existing data. calling save on resource.
This is my service.
app.factory('SubscriptionsService', function ($resource, $q) {
// $resource(url, [paramDefaults], [actions], options);
var resource = $resource('/api/subscriptions');
var factory = {};
factory.updateSubscriptions = function (updatedSubscriptions) {
console.log("SubscriptionsService: In updateSubscriptions. "+JSON.stringify( updatedSubscriptions));
var deferred = $q.defer();
resource.save(updatedSubscriptions,
function(response){
deferred.resolve(response);
},
function(response){
deferred.reject(response);
}
);
return deferred.promise;
}
return factory;
});
And API looks like the following.
exports.update = function (req, res) {
console.log("In Subscriptions API: Update invoked.");
if (req.body == null || req.body.items == null || req.body.items == 'undefined') {
return res.json({"status":0,"message":"no items present."});
}
var items = req.body.items;
console.log("About to call update on db."+items);
db.update(items,function(error,result,failedItems){
if(error)
return res.json({"status":-1,"message":error.message,"failedItem":failedItems});
return res.json({"status":1,"message":result,"failedItem":failedItems});
})
}
And controller as follows
$scope.confirmUpdates = function () {
if ($scope.updatedItems.length > 0) {
var updatedSubscriptionLevels = { "items": $scope.updatedItems };
var promise = SubscriptionsService.updateSubscriptions(updatedSubscriptionLevels);
promise.then(
function(response){
console.log("Response received from API.");
var statusCode = response.status;
console.log("statusCode: "+statusCode);
},
function(message){
console.log("Error message: "+message);
}
);
}
}
Strange part is data is getting updated. But promise receiving an error.
POST xxxxxx/testapi/subscriptions net::ERR_CONTENT_LENGTH_MISMATCH
I doubt I am not implementing/consuming resource in a right way. Can anybody advise?
Thanks.
I think I found the mistake.
There were couple of things I was not handling in a right way.
I didn't post my db code in mt question where the root cause was present.
if(itemsToUpdate!=null && itemsToUpdate.length>0){
var failedItems = []; // Holds failed items while updating.
console.log("Items to update: "+itemsToUpdate);
for(var index=0;index<itemsToUpdate.length;index++){
var item = itemsToUpdate[index];
console.log(item);
subscriptionLevels.update(
{_id:item._id},
{$set:{title:item.title,daysValid:item.daysValid,subscriptionFee:item.subscriptionFee}},
function(error,rowsEffected){
if(error){
console.log(error);
// Error occurred. Push it to failed items array.
failedItems.push({"title":item.title,"message":error.message});
}
else{
console.log("Number of rows updated: "+rowsEffected);
}
}
);
}
callback(null,"SUCCESS",failedItems);
}
else {
callback(null, "NO_ITEMS", []);
}
I missed if - else condition and invoking callback multiple times which was causing the issue.

Resources