Meteor async function in meteor-method with database update - javascript

for the last few hours, I have been trying to get an async method to play nice with meteor methods and its database.
While using wrapAsync works great for simple method calls, I struggling with getting it to work in this case.
Any help would be much appreciated.
https://docs.meteor.com/api/core.html#Meteor-wrapAsync
The async method in question:
chargebee.subscription.list({
limit : 5,
"plan_id[is]" : "basic",
"status[is]" : "active",
"sort_by[asc]" : "created_at"
}).request(function(error,result){
if(error){
//handle error
console.log(error);
}else{
for(var i = 0; i < result.list.length;i++){
var entry=result.list[i]
console.log(entry);
var subscription = entry.subscription;
var customer = entry.customer;
var card = entry.card;
}
}
});
What I tried and didn't work:
try {
var result = Meteor.wrapAsync(chargebee.subscription.list, chargebee.subscription)({
limit: 5,
"customer_id[is]": Meteor.userId(),
"status[is]": "active",
"sort_by[asc]": "created_at"
}).request();
if (result.list[0]) {
const subscription = result.list[0].subscription;
console.log("quantity", subscription.plan_quantity);
Subs.update(
{
owner_id: this.userId
}, {
$set: {
quantity: subscription.plan_quantity
}
}
);
}
} catch(error) {
console.log(error);
}

You should wrap in Meteor.wrapAsync the async method itself. In your code you're wrapping the chargebee.subscription.list only, and it isn't async (based on your example).
You should wrap .request() method instead (not its call):
// Call to `subscription.list()` extracted
// for better understanding
try {
var list = chargebee.subscription.list({
limit: 5,
"customer_id[is]": Meteor.userId(),
"status[is]": "active",
"sort_by[asc]": "created_at"
});
var result = Meteor.wrapAsync(list.request)();
// process result
} catch(error) {
// process error
}

Related

How to Tally Up Numerical Values and Produce One Value for all Documents in Mongo/Node

I am trying to do what should be a rather simple operation in my mongoDB/Node environment. Every document in the collection I'm targeting has a field "openBalance", which is a number value. All I want to do is find the totalOpenBalance by adding all of those together.
So far, in reviewing the MongoDB documentation, both $add and $sum seem to be used to perform an operation on the individual documents within the collection, rather than on the collection itself.
This leads me to wonder, is there a different way I should approach this? I've tried numerous constructions, but none work. Here is my function in full:
exports.getClientData = async function (req, res, next) {
let MongoClient = await require('../config/database')();
let db = MongoClient.connection.db;
let search, skip, pagesize, page, ioOnly = false, client;
let docs = [];
let records = 0;
if (_.isUndefined(req.params)) {
skip = parseInt(req.skip) || 0;
search = JSON.parse(req.search);
pagesize = parseInt(req.pagesize) || 0;
page = parseInt(req.page) || 0;
client = req.client || '';
ioOnly = true;
}
else {
skip = parseInt(req.query.skip) || 0;
search = req.body;
pagesize = parseInt(req.query.pagesize) || 0;
page = parseInt(req.query.page) || 0;
client = req.query.client || '';
}
search = {};
if (skip === 0) {
skip = page * pagesize;
}
if (client) {
let arrClient = [];
arrClient = client.split(",");
if (arrClient) {
// convert each ID to a mongo ID
let mongoArrClient = arrClient.map(client => new mongo.ObjectID(client));
if (mongoArrClient) {
search['client._id'] = { $in: mongoArrClient };
}
}
}
console.log(search);
let counter = 0;
let count = await db.collection('view_client_data').find(search).count();
let totalClients = await db.collection('view_client_data').find(search).count({ $sum: "client._id" });
console.log('totalClients', totalClients);
let totalOpenBalance = await db.collection('view_client_data').find(search).count({ $sum: { "$add" : "openBalance" } });
console.log('totalOpenBalance', totalOpenBalance);
db.collection('view_client_data').find(search).skip(skip).limit(pagesize).forEach(function (doc) {
counter ++; {
console.log(doc);
docs.push(doc);
}
}, function (err) {
if (err) {
if (!ioOnly) {
return next(err);
} else {
return res(err);
}
}
if (ioOnly) {
res({ sessionId: sessID, count: count, data: docs, totalClients: totalClients, totalOpenBalance: totalOpenBalance });
}
else {
res.send({ count: count, data: docs, totalClients: totalClients, totalOpenBalance: totalOpenBalance });
}
});
}
As you can see in the above code, I am getting the total number of clients with this code:
let totalClients = await db.collection('view_client_data').find(search).count({ $sum: "client._id" });
console.log('totalClients', totalClients);
That works perfectly, adding up the instances of a client and giving me the total.
Again, to be crystal clear, where I'm running into a problem is in summing up the numerical value for all of the openBalance values. Each document has a field, openBalance. All I want to do is add those up and output them in a variable titled totalOpenBalance and pass that along in the response I send, just like I do for totalClients. I have tried numerous options, including this:
let totalOpenBalance = await db.collection('view_client_data').find(search).count({ $sum: { "$add" : "openBalance" } });
console.log('totalOpenBalance', totalOpenBalance);
and this:
let totalOpenBalance = await db.collection('view_client_data').find(search).aggregate({ $sum: { "$add" : "openBalance" } });
console.log('totalOpenBalance', totalOpenBalance);
... but as I say, none work. Sometimes I get a circular reference error, sometimes an aggregate is not a function error, other times different errors. I've been wracking my brain trying to figure this out -- and I assume it shouldn't be that complicated once I understand the required syntax. How can I get my totalOpenBalance here?
By the way, the documents I'm targeting look something like this:
{
"_id": "3hu40890sf131d361f1ad908",
"client": {
"_id": "4ft9d366121j04563be0b01d6",
"name": {
"first": "John",
"last": "Smith"
}
},
"openBalance": 128,
"lastPurchaseDate": "2018-01-19T00:00:00.000Z"
},
$sum is an accumulator operator that must appear within a $group or $project aggregate pipeline stage. To also incorporate your search filter, you can include a $match stage in your pipeline.
let result = await db.collection('view_client_data').aggregate([
{$match: search},
{$group: {_id: null, totalOpenBalance: {$sum: '$openBalance'}}}
]).next();
console.log(result.totalOpenBalance);
I think $group is what you're looking for.
So for example to calculate all the openBalance fields
db.view_client_data.aggregate(
[
{
$group: {
_id : null
totalOpenBalance: { $sum: "$openBalance" },
}
}
]
)
this should give you an object back like {totalOpenBalance: 900}
Here is the mongodb documentation for some more examples
https://docs.mongodb.com/manual/reference/operator/aggregation/group/#pipe._S_group

asyncronous functions run in order when inside of a loop using promises

I have a method that runs in a for loop of data that needs to be inserted into a mongodb database.
before inserting i need to query the database to see if a similar record exists so that i can give it the same code else i need to find the max code of the record and give the inserted method the next highest code
the output i need is Code, 0, 2, insert for first record then Code, 1, 2, insert but the output i am receiving is code,0 code, 0 code, 0 code, 0 2,2,2,2 which tells me that the each action is running the amount of times in the loop one after each other and not in the sequence that i need it
//First Promise
const BatchCheckPromise = new Promise((resolve) => {
const Code = "";
var query = {
BatchCode: {
$eq: Row.BatchCode
}
}
//Mongo Database Query To Get Code From The Batch
var BatchRow = db.collection("ap_transaction_line").find(query).toArray();
BatchRow.then(function(db_data){
console.log("Code");
console.log(db_data.length);
//Get code if in database
if (db_data.length > 0) {
Code = db_data[0].Code;
}
resolve(Code)
});
});
//Second Promise
async function MaxCheckPromise(Code) {
return new Promise(
(resolve, reject) => {
//If code is still null after checking the database
if (Code == "" || Code == null) {
var Code = "";
var query = {
Prefix: {
$eq: Row.Prefix
}
}
var sort = {
Code: -1
};
//Mongo Database query for max code in the database
var MaxRow = db.collection("ap_transaction_line").find(query).sort(sort).limit(1).toArray();
MaxRow.then(function(db_data){
Code = db_data[0].Code;
Code++;
//Increment by 1 for new code
resolve(Code);
/////////
});
}
else {
resolve(Code);
}
}
)
}
//Insert function
async function InsertInToDataBase() {
try {
//first promise to query the batchcode
let BatchCode = await BatchCheckPromise;
//second promise to query the max code if no batchcode
let MaxCode = await MaxCheckPromise(BatchCode);
console.log(MaxCode);
//Insert into Database with new Code
if (Row.TimeStamp == "U") {
//vars
//db query
var db_collection = "ap_transaction_line";
var db_query = {
LocalID: Row.LocalID
};
var db_data = {
TimeStamp: Row.TimeStamp,
Error: Row.Error,
SyncID: Row.SyncID,
DateCreated: Row.DateCreated,
BatchCode: Row.BatchCode,
TransCode: Row.TransCode,
ConsCode: Row.ConsCode,
DebitCode: Row.DebitCode,
Prefix: Row.Prefix,
Code: MaxCode,
ToAcc: Row.ToAcc,
FromAcc: Row.FromAcc,
Vendor_Client: Row.Vendor_Client,
Date: Row.Date,
Description: Row.Description,
Qty_Calls: Row.Qty_Calls,
Price_Rate: Row.Price_Rate,
TotalEX: Row.TotalEX,
Ref: Row.Ref,
Detail: Row.Detail,
Username: Row.Username,
PaidStatus: Row.PaidStatus,
Period: Row.Period,
PeriodAuth: Row.PeriodAuth,
BankRecon: Row.BankRecon,
PDFName: Row.PDFName,
Auth: Row.Auth,
InvItem: Row.InvItem,
InvPicName: Row.InvPicName,
EpsNum: Row.EpsNum,
BatchGroup: Row.BatchGroup
};
m_connect("updateOne", DbName.DBname, db_collection, db_query, "", db_data, "", function (db_connect) {
//console.log('db_connect');
//console.log(db_connect);
});
} else if (Row.TimeStamp == "") {
//db query
var db_collection = "ap_transaction_line";
var db_query = {
SyncID: Row.SyncID
};
var db_data = {
TimeStamp: Row.TimeStamp,
Error: Row.Error,
SyncID: Row.SyncID,
DateCreated: Row.DateCreated,
BatchCode: Row.BatchCode,
TransCode: Row.TransCode,
ConsCode: Row.ConsCode,
DebitCode: Row.DebitCode,
Prefix: Row.Prefix,
Code: MaxCode,
ToAcc: Row.ToAcc,
FromAcc: Row.FromAcc,
Vendor_Client: Row.Vendor_Client,
Date: Row.Date,
Description: Row.Description,
Qty_Calls: Row.Qty_Calls,
Price_Rate: Row.Price_Rate,
TotalEX: Row.TotalEX,
Ref: Row.Ref,
Detail: Row.Detail,
Username: Row.Username,
PaidStatus: Row.PaidStatus,
Period: Row.Period,
PeriodAuth: Row.PeriodAuth,
BankRecon: Row.BankRecon,
PDFName: Row.PDFName,
Auth: Row.Auth,
InvItem: Row.InvItem,
InvPicName: Row.InvPicName,
EpsNum: Row.EpsNum,
BatchGroup: Row.BatchGroup
};
//m_connect("queryAll", db_collection, "", "", function(db_connect){
m_connect("insertone", DbName.DBname, db_collection, db_query, "", db_data, "", function (db_connect) {
//console.log('db_connect');
//console.log(db_connect);
console.log("Insert");
});
}
}
catch (error) {
console.log(error.message);
}
}
//Run the functions async
(async () => {
await InsertInToDataBase();
})();
I suggest you to rely on libraries like Axios to manage ajax call with promises, because it adds nice feature like serial calls (the one you need) and parallels calls.

For loop returning null in node js

I am using googles api to get 3 fields,
name rating and place review. Since place review uses another api call I created it as a function to get the data inside my place_review.
response.results.forEach((entry)=>{
var restaurantName = {
"name" : entry.name,
"rating" : entry.rating,
"place_review" : placeReview(entry.place_id)
}
arr.push(restaurantName);
console.log(restaurantName);// shows only name and rating
});
The extra function
function placeReview(place_id){
console.log(place_id) // i see all place id via forloop.
googlePlaces.placeDetailsRequest({placeid: place_id},function(error,response){
if (error) throw error;
return response.result.reviews[0].text;
});
}
When I run the code , i get name : somename, rating : 4.5 and place_review : null
I thought the forloop will call the function and the return of the function will append the string to "place_review" not sure what I am doing wrong.
The placeDetailsRequest api is asynchronous, meaning it is initiated but doesn't complete right away. Consequently, the line:
return response.result.reviews[0].text;
is meaningless because there is nothing to return to.
Your console.log(restaurantName); will be run before the request completes so you will only get an 'undefined'.
To fix this, you will need to do something a fair bit more complicated ( will assume, for now that Promises are something you are not familiar with but that is typically the way people go once they are comfortable with the way asynchronous programming works):
response.results.forEach((entry)=>{
var restaurantName = {
"name" : entry.name,
"rating" : entry.rating,
"place_review" : undefined // will be filled in later
}
placeReview(entry.place_id, restaurantName);
});
function placeReview(place_id, obj){
console.log(place_id) // i see all place id via forloop.
googlePlaces.placeDetailsRequest({placeid: place_id},function(error,response){
if (error) throw error;
obj.place_review = response.result.reviews[0].text;
console.log(obj);
});
}
Of course, you will only see the console results as each request finishes.
This uses a technique called 'callbacks' to manage the information that is only available at a later time. There is lots of information about this online and on SO.
Here's a version that you can run to see the technique in action - very similar to above but hard-wired some data.
var list = [
{ name: 'Aaron', rating: '14', place_id: 21 },
{ name: 'Brad', rating: '33', place_id: 33 }
];
list.forEach(function (entry) {
var restaurantName = {
"name" : entry.name,
"rating" : entry.rating,
"place_review" : undefined // will be filled in later
};
placeReview(entry.place_id, restaurantName);
});
function placeDetailsRequest(idObj, cb) {
setTimeout(function() {
var result = (idObj.placeid === 21) ? 'def' : 'abc';
cb(null, { result: { reviews: [{ text: result}]}});
}, 2000);
}
function placeReview(place_id, obj) {
console.log(place_id); // i see all place id via for loop.
placeDetailsRequest( { placeid: place_id }, function(error,response){
if (error) throw error;
obj.place_review = response.result.reviews[0].text;
console.log(obj);
});
}
Further Edit to Show Promise Solution:
var list = [
{ name: 'Aaron', rating: '14', place_id: 21 },
{ name: 'Brad', rating: '33', place_id: 33 }
];
var allPromises = list.map(function (entry) {
var restaurantName = {
"name" : entry.name,
"rating" : entry.rating,
"place_review" : undefined // will be filled in later
};
return placeReview(entry.place_id, restaurantName);
});
Promise.all(allPromises)
.then(results => console.log(results))
.catch(error => console.log(error));
function placeReview(place_id, obj) {
return new Promise((resolve, reject) => {
// Here's where you would do your google api call to placeDetailsRequest
// if error then reject the promise, otherwise resolve it with your results
//googlePlaces.placeDetailsRequest({placeid: place_id},function(error,response){
// if (error) {
// reject(error);
// } else {
// obj.place_review = response.result.reviews[0].text;
// resolve(obj);
// }
//});
// simulating with delayed response
setTimeout(() => {
var result = (place_id === 21) ? 'def' : 'abc';
obj.place_review = result;
resolve(obj);
}, 2000);
});
}
There are other ways to approach this, but in this solution I am resolving the promise with the completed object. The Promise.all() function then either shows the successful results or the catch will display an error.
Promises
You could use Q in order to implement promises in your place review function:
function placeReview (place_id) {
var deferred = Q.defer();
googlePlaces.placeDetailsRequest({placeid: place_id},function(error,response){
if (error) deferred.reject(err);
else deferred.resolve(response.result.reviews[0].text);
});
return deferred.promise // the promise is returned
}
then you can invoke that function in your processing logic like this:
response.results.forEach((entry)=>{
placeReview(entity.place_id).then(function(text) {
var restaurantName = {
"name" : entry.name,
"rating" : entry.rating,
"place_review" : text
}
arr.push(restaurantName);
console.log(restaurantName);// shows only name and rating
});
});
you will not push the restaurant details till your call to the API is complete.
Async option
You could use async to coordinate your calls:
This function will be executed once per each entry:
function placeReview(entry, callback){
console.log(entry.place_id)
googlePlaces.placeDetailsRequest({entry.placeid: place_id},function(error,response){
if (error) throw error;
//this call back retrives the restaurant entity that async stack in a list
callback(null,{
"name" : entry.name,
"rating" : entry.rating,
"place_review" : response.result.reviews[0].text
});
});
}
Then you ask async to iterate your result array and execute placeReview for each one. Async will gather results in an array:
async.map(response.results, placeReview, function(err, results) {
// results will have the results of all 2
console.log(results); //this is your array of restaurants
});

Javascript function to return Elasticsearch results

I'm trying to write a JavaScript function that returns results of an Elasticsearch v5 query. I can't figure out where and how to include 'return' in this code. With the following, segmentSearch(id) returns a Promise object,{_45: 0, _81: 0, _65: null, _54: null}.
_65 holds an array of the correct hits, but I can't figure out how to parse it. The console.log(hits) produces that same array, but how can I return it from the function?
var elasticsearch = require('elasticsearch');
var client = new elasticsearch.Client({
host: 'localhost:9200',
log: 'trace'
});
segmentSearch = function(id){
var searchParams = {
index: 'myIndex',
type: 'segment',
body: {
query: {
nested : {
path : "properties",
query : {
match : {"properties.source" : id }
},
inner_hits : {}
}
}
}
}
return client.search(searchParams).then(function (resp) {
var hits = resp.hits.hits;
console.log('hits: ',hits)
return hits;
}, function (err) {
console.trace(err.message);
});
}
I would instanitate a new array outside of your client.search function in global scope and array.push your 'hits' Then access your newly filled array.
let newArr = [];
client.search(searchParams).then(function (resp) {
for(let i = 0; i < resp.hits.hits.length; i++){
newArr.push(resp.hits.hits[i]);
}
console.log('hits: ',newArr)
return newArr;
}, function (err) {
console.trace(err.message);
});
First of all, elasticsearch js client is working with Promise ( I think using callback is also possible).
Using Promise is a good way to handle asynchronous computation.
In your question, you have already done something with a promise:
var search = function(id)
{
var searchParams = { /** What your search is **/}
return client.search(searchParams)
}
This call is returning a Promise.
If we consider handleResponse as the function in your then
var handleResponse = function(resp)
{
var hits = resp.hits.hits;
console.log('hits: ',hits)
return hits; //You could send back result here if using node
}
In your code, handleResponse is called once the promise is fullfield. And in that code you are processing data. Don't forget you are in asynchronious, you need to keep working with promise to handle hits.
By the way, in your question, what you have done by using "then" is chaining Promise. It is normal to have Promise.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
Promise.prototype.then is returning a Promise.
var segmentSearch = function ( id )
{
return search
.then( handleResponse //here you got your hit )
.then( function ( hits )
{
console.log(hits) //and here you have done something with hits from search.
})
}
I neglected to post my fix, sorry about that. The sequence is to create searchParams, perform client.search(searchParams) which returns a Promise of hits, then process those hits:
segmentSearch = function(obj){
// retrieve all segments associated with a place,
// populate results <div>
let html = ''
var plKeys = Object.keys(obj)
var relevantProjects = []
for(let i = 0; i < plKeys.length; i++){
relevantProjects.push(obj[plKeys[i]][0])
var searchParams = {
index: 'myIndex',
type: 'segment',
body: {
query: {
nested : {
path : "properties",
query : {
match : {"properties.source" : id }
},
inner_hits : {}
}
}
}
}
client.search(searchParams).then(function (resp) {
return Promise.all(resp.hits.hits)
}).then(function(hitsArray){
...write html to a <div> using hits results
}
}

How to delay a promise until async.each completes?

How do I delay a promise until an asynchronous operation has completed? I am using async and bluebird libraries. As soon as I boot up my program, the done() function returns an error that is an empty or nearly empty 'masterlist' object. Why isn't async waiting until the iterator has finished its operation?
// bundler.js
var masterlist = {
"children": []
, "keywords": []
, "mentions": 0
, "name" : "newsfeed"
, "size" : 0
}
// initialize() returns a promise with the populated masterlist
exports.initialize = function() {
return new Promise(function(resolve, reject) {
// pullBreakingNews() returns a promise with the breaking news articles
nytimes.pullBreakingNews().then(function(abstracts) {
async.map(abstracts, iterator, done);
function iterator(item, callback) {
alchemyapi.entities('text', item, {}, function(response) {
// initialize each entity with masterlist
response.entities.forEach(function(entity) {
masterlist.children[masterlist.children.length] =
{
"abstract": item
, "children": []
, "name": entity.text
, "size": 0
};
masterlist.size += 1;
masterlist.keywords.push(entity.text);
});
callback(masterlist);
});
};
function done(err, results) {
if (err) {
console.log("ERROR: ", err);
} else {
resolve(results);
}
};
});
});
};
Firehose.js is the module that calls initializer(). I believe that firehose gets run first, and the promise is called in the process.
server.js => firehose.js => bundler.js => nytimes api
// firehose.js
// Compares entities to Twitter stream, counts every match
exports.aggregator = function(callback) {
bundler.initialize().then(function(masterlist) {
t.stream('statuses/filter', { track: masterlist.keywords }, function(stream) {
// read twitter firehose for incoming tweets.
stream.on('data', function(tweet) {
var tweetText = tweet.text.toLowerCase();
// sift through each tweet for presence of entities
masterlist.children.forEach(function(parentObject) {
// if the entity exists in the tweet, update counters
if (tweetText.indexOf(parentObject.name.toLowerCase()) !== -1) {
parentObject.size += 1;
masterlist.mentions += 1;
callback(masterlist);
}
});
});
});
});
};
Thanks very much for any help.
Please don't mix callbacks and promises, only use either one.
// Do this somewhere else, it is only needed once
// it adds promise returning versions of all alchemy methods for you
Promise.promisifyAll(require('alchemy-api').prototype);
exports.initialize = function() {
return nytimes.pullBreakingNews().map(function(abstract) {
// Note that it is entitiesAsync that is a promise returning function
return alchemyapi.entitiesAsync('text', abstract, {}).then(function(response){
response.entities.forEach(function(entity) {
masterlist.children[masterlist.children.length] =
{
"abstract": abstract
, "children": []
, "name": entity.text
, "size": 0
};
masterlist.size += 1;
masterlist.keywords.push(entity.text);
});
});
}).return(masterlist);
};
Also your initialize function isn't checking if it is initialized already
The iterator's callback accepts an error as the first argument. You should pass a falsy value (like a null) there instead of masterlist if there's no error.
function iterator(item, callback) {
alchemyapi.entities('text', item, {}, function(response) {
// initialize each entity with masterlist
response.entities.forEach(function(entity) {
masterlist.children[masterlist.children.length] =
{
"abstract": item
, "children": []
, "name": entity.text
, "size": 0
};
masterlist.size += 1;
masterlist.keywords.push(entity.text);
});
callback(null, masterlist);
});
};

Categories

Resources