Synchronising Lodash and mongoose query in nodejs - node.js

I am trying to accomplish synchronisation in Mongoose query inside a _.each lodash function like this -
let commentContainer = [];
let comments = {..} //json object having comments
_.each(comments, comment => {
User.findOne({_id: comment.createdBy}).exec()
.then(function(commentor){
var c = {
text: comment.text,
votes: comment.votes.length,
commentor: {
name: commentor.name,
profilePhoto: commentor.profilePhoto,
id: commentor._id
}
}
commentContainer.push(c);
});
});
}
console.log(commentContainer); //it shows []
How can I achieve it, I tried using setTimeout function by giving a delay but it does not seems a valid procedure.

modify your code like this way:
let fun = async() => {
let commentContainer = [];
let comments = {..} //json object having comments
await _.each(comments, comment => {
User.findOne({_id: comment.createdBy}).exec()
.then(function(commentor){
var c = {
text: comment.text,
votes: comment.votes.length,
commentor: {
name: commentor.name,
profilePhoto: commentor.profilePhoto,
id: commentor._id
}
}
commentContainer.push(c);
});
});
}
console.log(commentContainer); //it shows []
}
make your function async and use await keywoed when you need to wait for the process to complete before next iteration

It's because Node.js is asynchronous.You should use async/await or promise or callback when you are dealing with non-blocking call like DB-call or Http client call.
let comments = {..} //json object having comments
console.log(findUSer(comments)); // print result
async function findUSer(comments){
let commentContainer = [];
await _.each(comments, comment => {
User.findOne({_id: comment.createdBy}).exec()
.then(function(commentor){
var c = {
text: comment.text,
votes: comment.votes.length,
commentor: {
name: commentor.name,
profilePhoto: commentor.profilePhoto,
id: commentor._id
}
}
commentContainer.push(c);
});
});
}
return commentContainer;
}

Related

nodeJS: how to call an async function within a loop in another async function call

I am trying to call one async function from inside a loop run by another async function. These functions call APIs and I am using request-promise using nodeJS.
functions.js file
const rp = require("request-promise");
// function (1)
async email_views: emailId => {
let data = {};
await rp({
url: 'myapiurl',
qs: { accessToken: 'xyz', emailID: emailId },
method: 'GET'
})
.then( body => { data = JSON.parse(body) })
.catch( error => { console.log(error} );
return data;
};
The above JSON looks like this:
...
data:{
records: [
{
...
contactID: 123456,
...
},
{
...
contactID: 456789,
...
}
]
}
...
I am running a loop to get individual record, where I am getting a contactID associated with each of them.
// function#2 (also in functions.js file)
async contact_detail: contactId => {
let data = {};
await rp({
url: 'myapiurl2',
qs: { accessToken: 'xyz', contactID: contactId },
method: 'GET'
})
.then( body => { data = JSON.parse(body) })
.catch( error => { console.log(error} );
return data;
};
The above function takes one contactId as parameter and gets that contact's detail calling another API endpoint.
Both functions work fine when they are called separately. But I am trying to do it inside a loop like this:
...
const result = await email_views(99999); // function#1
const records = result.data.records;
...
let names = "";
for( let i=0; i<records.length; i++) {
...
const cId = records[i].contactID;
const contact = await contact_detail(cId); // function#2
names += contact.data.firstName + " " + contact.data.lastName + " ";
...
}
console.log(names);
...
The problem is I am only getting the first contact back from the above code block, i.e. even I have 20 records from function#1, in the loop when I am calling contact_detail (function#2) for each contactID (cId), I get contact detail once, i.e. for the first cId only. For rest I get nothing!
What is the correct way to achieve this using nodeJs?
UPDATE:
const { App } = require("jovo-framework");
const { Alexa } = require("jovo-platform-alexa");
const { GoogleAssistant } = require("jovo-platform-googleassistant");
const { JovoDebugger } = require("jovo-plugin-debugger");
const { FileDb } = require("jovo-db-filedb");
const custom = require("./functions");
const menuop = require("./menu");
const stateus = require("./stateus");
const alexaSpeeches = require("./default_speech");
const app = new App();
app.use(new Alexa(), new GoogleAssistant(), new JovoDebugger(), new FileDb());
let sp = "";
async EmailViewsByContactIntent() {
try {
const viewEmailId =
this.$session.$data.viewEmailIdSessionKey != null
? this.$session.$data.viewEmailIdSessionKey
: this.$inputs.view_email_Id_Number.value;
let pageIndex =
this.$session.$data.viewEmailPageIndex != null
? this.$session.$data.viewEmailPageIndex
: 1;
const result = await custom.email_views_by_emailId(
viewEmailId,
pageIndex
);
const records = result.data.records;
if (records.length > 0) {
const totalRecords = result.data.paging.totalRecords;
this.$session.$data.viewEmailTotalPages = totalRecords;
sp = `i have found a total of ${totalRecords} following view records. `;
if (totalRecords > 5) {
sp += `i will tell you 5 records at a time. for next 5 records, please say, next. `;
this.$session.$data.viewEmailIdSessionKey = this.$inputs.view_email_Id_Number.value;
this.$session.$data.viewEmailPageIndex++;
}
for (let i = 0; i < records.length; i++) {
const r = records[i];
/* Here I want to pass r.contactID as contactId in the function contact_detail like this: */
const contact = await custom.contact_detail(r.contactID);
const contact_name = contact.data.firstName + " " + contact.data.lastName;
/* The above two lines of code fetch contact_name for the first r.contactID and for the rest I get an empty string only. */
const formatted_date = r.date.split(" ")[0];
sp += `contact ID ${spellOut_speech_builder(
r.contactID
)} had viewed on ${formatted_date} from IP address ${
r.ipAddress
}. name of contact is, ${contact_name}. `;
}
if (totalRecords > 5) {
sp += ` please say, next, for next 5 records. `;
}
} else {
sp = ``;
}
this.ask(sp);
} catch (e) {
this.tell(e);
}
}
I am building an alexa skill using JOVO framework and nodeJS.
UPDATE #2
As a test, I only returned the contactId which I am passing to the contact_detail function and I am getting the correct value back to the above code under my first UPDATE.
async contact_detail: contactId => {
return contactId;
}
It seems even after getting the value right, the function is somehow failing to execute. However, the same contact_detail function works perfectly OK, when I am calling it from another place. Only doesn't not work inside a loop.
What could be the reason?
I must be missing something but don't know what!
You are mixing async await and promises together which is causing you confusion. You typically would use one of the other(as async await effectivly provides syntax sugar so you can avoid dealing with the verbose promise code) in a given location.
Because you mixed the two you are in a weird area where the behavior is harder to nail down.
If you want to use async await your functions should look like
async contact_detail: contactId => {
try {
const body = await rp({
url: 'myapiurl2',
qs: { ... }
});
return JSON.parse(body);
} catch(e) {
console.log(e);
//This will return undefined in exception cases. You may want to catch at a higher level.
}
};
or with promises
async contact_detail: contactId => {
return rp({
url: 'myapiurl2',
qs: { ... }
})
.then( body => JSON.parse(body))
.catch( error => {
console.log(error);
//This will return undefined in exception cases. You probably dont want to catch here.
});
};
Keep in mind your current code executing the function will do each call in series. If you want to do them in parallel you will need to call the function a bit differently and use something like Promise.all to resolve the result.
Here you go:
...
const result = await email_views(99999); // function#1
const records = result.data.records;
...
let names = "";
await Promise.all(records.map(async record => {
let cId = record.contactID;
let contact = await contact_detail(cId);
names += contact.data.firstName + " " + contact.data.lastName + " ";
});
console.log(names);
...
I'm posting this as an answer only because I need to show you some multi-line code as part of throubleshooting this. Not sure this solves your issue yet, but it is a problem.
Your contact_detail() function is not properly returning errors. Instead, it eats the error and resolves with an empty object. That could be what is causing your blank names. It should just return the promise directly and if you want to log the error, then it needs to rethrow. Also, there's no reason for it to be declared async or to use await. You can just return the promise directly. You can also let request-promise parts the JSON response for you too.
Also, I notice, there appears to be a syntax error in your .catch() which could also be part of the problem.
contact_detail: contactId => {
return rp({
url: 'myapiurl2',
qs: { accessToken: 'xyz', contactID: contactId },
json: true,
method: 'GET'
}).catch( error => {
// log error and rethrow so any error propagates
console.log(error);
throw error;
});
};
Then, you would call this like you originally were (note you still use await when calling it because it returns a promise):
...
const result = await email_views(99999); // function#1
const records = result.data.records;
...
let names = "";
for( let i=0; i<records.length; i++) {
...
const cId = records[i].contactID;
const contact = await contact_detail(cId);
names += contact.data.firstName + " " + contact.data.lastName + " ";
...
}
console.log(names);
...

node.js blocking, non-blocking issue in for statement

How can I save all of the json into my mongoldb?
Strangely, only the first value is stored every time.
It might be blocking/non-blocking issue.
json = {
["name":"Karl","id":"azo0"],
["name":"Robert","id":"bdd10"],
["name":"Joan","id":"difj90"],
["name":"Hallyn","id":"fmak88"],
["name":"Michael","id":"vma91"]
};
for(var i = 0; i < json.length; i++){
id = json[i].id;
name = json[i].name;
var ctx = {"id":id,"name":name};
db.json_db.count(ctx).exec(function(err, count) {
if(count < 1){
var model = new User({
"name":json[i].name,
"id":json[i].id
});
model.save(function(){
console.log("ok"+i);
});
}
});
};
After inserting, all of datas are filled with ["name":"Karl","id":"azo0"]
To check out console.log("ok"+i), it prints always "ok0" not "ok1", "ok2", "ok3"... etc..
How can I prevent this issue?
Incase you're using Async package, this is an best way to solve your problem...
async.eachSeries(json, (item, done) => {
let user = new User(
{
"name":json[i].name,
"id":json[i].id
},
(err, user) => {
if(err){
// handle err
}
return done();
}
);
});
.exec() tells me you're using Mongoose. So your loop can rewritten as:
const json = [
{name: "Karl", id: "azo0"},
{name: "Robert", id: "bdd10"},
{name: "Joan", id: "difj90"},
{name: "Hallyn", id: "fmak88"},
{name: "Michael", id: "vma91"}
];
for (const item of json) {
const count = await db.json_db.count(item).exec()
if (!count) {
await new User(item).save()
}
}
Error handling omitted.
See http://exploringjs.com/es2016-es2017/ch_async-functions.html

Sequelize Transaction inside forEach loop

I'm trying to use transaction inside forEach loop using async/await syntax of Node 7.0+
When I try to print committed transaction response in console, I'm able to see the values but those same values are not committed in to DB.
Below is the code :
documentInfo.forEach(async (doc) => { // array of documentInfo
var frontImgName = await module.exports.uploadImage(docFiles, doc.front, req, res )
var backImgName = await module.exports.uploadImage(docFiles, doc.back, req, res )
var checkKycDoc = await KYCDocument.findOne({
where: {
kyc_id: checkUserKyc.dataValues.kyc_id,
user_id: checkUserKyc.dataValues.user_id
}
})
if (checkKycDoc) { //update
var updateDocument = await KYCDocument.update({
document_name: doc.document_name,
front_image: frontImgName,
back_image: backImgName
}, {
where: {
kyc_id: checkUserKyc.dataValues.kyc_id,
user_id: checkUserKyc.dataValues.user_id
},
}, {transaction})
log('updateDocument', updateDocument.dataValues)
} else { // insert
var newKycDocument = await new KYCDocument({
kyc_id: checkUserKyc.dataValues.kyc_id,
user_id: checkUserKyc.dataValues.user_id,
document_name: doc.document_name,
front_image: frontImgName,
back_image: backImgName,
status: true
}, {transaction})
log('newKycDocument', newKycDocument.dataValues)
}
if (rowCount === documentInfo.length) {
await transaction.commit() // transaction is printed on this line
log('KYC has been uploaded successfully')
helpers.createResponse(res, constants.SUCCESS,
messages.KYC_UPLOAD_SUCCESS,
{'error': messages.KYC_UPLOAD_SUCCESS}
)
} else {
rowCount++
}
})
The issue was in the create method.
To resolve the issue I had to create a new row using:
var newKycDocument = await KYCDocument.create({
kyc_id: checkUserKyc.dataValues.kyc_id,
user_id: checkUserKyc.dataValues.user_id,
document_name: doc.document_name,
front_image: frontImgName,
back_image: backImgName
}, {transaction})
I was missing the .create method.

rxjs - searching in observables

I am trying to implement a simple search scheme to search for values in one observable from another observable. The buildLookup function below builds a lookup table using values from an observable:
// Build lookup table from an observable.
// Returns a promise
function buildLookup(obs, keyName, valName) {
const map = new Map();
obs.subscribe((obj) => map.set(obj[keyName], obj[valName]));
// use concat to force wait until `obs` is complete
return obs.concat(Observable.from([map])).toPromise();
}
Then I have another function this uses the result of this function (a promise):
// Lookup in a previously built lookup table.
function lookup(source, prom, keyName, fieldName) {
return source.map((obj) => {
const prom2 = prom.then((map) => {
return lodash.assign({}, obj, { [fieldName]: map.get(String(obj[keyName])) });
});
return Observable.fromPromise(prom2);
})
.flatMap((x) => x);
}
For some reason, this implementation does not work, and every other lookup seems to fail. Could you someone guide me on:
what is wrong with this code, and
whether there is a better way to implement something like this?
Thanks a bunch in advance for your help!
I am attaching my test code below:
"use strict";
const lodash = require("lodash");
const rxjs = require("rxjs");
const chai = require("chai");
const Observable = rxjs.Observable;
const assert = chai.assert;
const assign = lodash.assign;
describe("search", () => {
it("simple search", (done) => {
let nextId = 1, nextId2 = 1;
const sourceObs = Observable.interval(5).take(5).map((i) => {
const id = nextId++;
return { id: `${id}` };
});
const searchableObs = Observable.interval(5).take(5).map((i) => {
const id = nextId2++;
return Observable.from([
{ id: `${id}`, code: "square", val: id * id },
]);
}).flatMap((x) => x);
const results = [];
const verifyNext = (x) => {
assert.isDefined(x);
results.push(x);
};
const verifyErr = (err) => done(err);
const verifyComplete = () => {
assert.equal(results.length, 5);
try {
results.forEach((r) => {
console.log(r);
// assert.equal(r.val, r.id*r.id); <== *** fails ***
});
} catch (err) {
done(err);
}
done();
};
// main
const lookupTbl = buildLookup(searchableObs, "id", "val"); // promise that returns a map
lookup(sourceObs, lookupTbl, "id", "val")
.subscribe(verifyNext, verifyErr, verifyComplete)
;
});
});
// output
// { id: '1', val: 1 }
// { id: '2', val: undefined }
// { id: '3', val: 9 }
// { id: '4', val: undefined }
// { id: '5', val: 25 }
So, a bunch of things to address here.
The main issue is that you are doing side-effects in your sourceObs and searchableObs observables, and it is not published, so the side-effects happen multiple times because you subscribe multiple times, giving you a wrong map entirely. For instance, I get maps like:
{"1" => 1, "4" => 16, "7" => 49, "12" => 144}
But you are doing something so trivial that you should really not use mutable variables.
To solve this, here is how you can create the proper observables:
const sourceObs = Rx.Observable.range(1, 5).map(i => ({ id: `${i}` }));
const searchableObs = Rx.Observable.range(1, 5).map(i =>
({ id: `${i}`, code: "square", val: i * i })
);
There is no reason to use a variable since range returns the numbers 1, 2, ...
And your use of o.map(_ => Rx.Observable.from(...)).concatMap(e => e) is really just the same as o...
While I'm here, this is a simplified version of your correct but clumsy functions:
// so buildLookup just needs to return a map once it's finished populating it
function buildLookup(obs, keyName, valName) {
// following your style here, though this could be done using `scan`
const map = new Map();
obs.subscribe((obj) => map.set(obj[keyName], obj[valName]));
// instead of your promise, I just wait for `obs` to complete and return `map` as an observable element
return obs.ignoreElements().concat(Rx.Observable.of(map));
}
// and lookup just needs to wait for the map, and then populate fields in the object
function lookup(source, prom, keyName, fieldName) {
return prom
.concatMap(map => source.map(obj => ({ obj: obj, map: map })))
.map(({ obj, map }) => lodash.assign({}, obj, { [fieldName]: map.get(String(obj[keyName])) }))
;
}
This should work for you.

sequelize.js - Find by id and return result

I have a function,
var findUserDevice = function(userDeviceId){
var device = db.DeviceUser.find({
where: {
id: userDeviceId
}
}).then(function(device) {
if (!device) {
return 'not find';
}
return device.dataValues;
});
};
but this function does not return anything...
var UserDevice = findUserDevice(req.body.deviceUserId);
console.log(UserDevice);// undefined
The operation you are trying to do is async, which means that you need to use a callback. Since sequelize is build on top of Promises, you should actually write your code like this :
var findUserDevice = function(userDeviceId){
// return the promise itself
return db.DeviceUser.find({
where: {
id: userDeviceId
}
}).then(function(device) {
if (!device) {
return 'not find';
}
return device.dataValues;
});
};
And later use it like :
findUserDevice(req.body.deviceUserId).then( function(UserDevice) {
console.log(UserDevice);
});
It's 2020, async & await are becoming more popular. You can change your code to:
const findUserDevice = async function (userDeviceId) {
const device = await db.DeviceUser.findOne({
where: {
id: userDeviceId
}
});
if (device === null) {
return 'device not found';
}
return device.dataValues;
};
(async () => {
// ...
const UserDevice = await findUserDevice(req.body.deviceUserId);
console.log(UserDevice);
// ...
})()
IMHO, the code above is way more readable.
If you are getting undefined instead of 'not find' on the console, it means your function is returning a value. The problem might be dataValues is actually undefined. You need to check for the content of device.
Hint: Try returning just device or device.id
PS. If you want to do the search based on id, should go for findById() function of your model.
var device = db.DeviceUser.findById(userDeviceId).then(function(device) {
if (!device) {
return 'not find';
}
return device.dataValues;
});
This function received params id, this worker for me:
const { customer } = require('../models');
const get = async function(req, res){
let id = req.params.id;
[err, singleCustomer] = await to(customer.findByPk(id, { raw : true }));
return ReS(res, { message :'Obtener cliente: : ', data : JSON.stringify(singleCustomer) });
}

Resources