Improve code with a Promise for a mongodb request - node.js

I'm new to javascript (and coding) and I'm looking into Promises.
I have the following working code:
router.get("/test", function(req, res){
var mainCategory = new Promise(function(resolve, reject){
Maincategory.find().populate("subcategory").exec(function(err, allMaincategories){
if (err) {
console.log("error 1");
reject("error 2");
}
else {
resolve(allMaincategories);
}
});
});
var itemQuery = new Promise(function(resolve, reject){
Items.find({}, function(err, allItems){
if (err) {
console.log("error 3");
reject("error 4");}
else {
resolve(allItems);
reject("error 5");
}
});
});
Promise.all([
mainCategory,
itemQuery
]).then(function(allQueries){
console.log(allQueries);
var allCategories = allQueries[0];
var allItems = allQueries[1]
var userId = "true";
var show = "list";
res.render("test.ejs", {
allCategories: allCategories,
allItems: allItems,
userId: userId,
show: show
});
}).catch(function(error){
console.log("error 6");
res.render("error.ejs", {error: error});
});
});
Question 1: Is it correct to use a callback inside a promise like that?
Question 2: Is there a (shorter) way to put all mongoose requests in one promise?

Mongoose queries return Promises already - since you're using Promises, better to just use the Promise it returns rather than to construct a new one to use with the callback:
Promise.all([
Maincategory.find().populate("subcategory").exec(),
Items.find({})
]).then(function(allQueries){
// ...

mongoose already supports promises natively, so there's no reason at all to create a promise like you are doing. This:
var mainCategory = new Promise(function(resolve, reject){
Maincategory.find().populate("subcategory").exec(function(err, allMaincategories){
if (err) {
console.log("error 1");
reject("error 2");
}
else {
resolve(allMaincategories);
}
});
});
should be replaced with
const subcatPromise = Maincategory.find().populate("subcategory").exec();
and
var itemQuery = new Promise(function(resolve, reject){
Items.find({}, function(err, allItems){
if (err) {
console.log("error 3");
reject("error 4");}
else {
resolve(allItems);
reject("error 5");
}
});
});
is equivalent to
const itemsPromise = Items.find({});

router.get("/test", async function (req, res) {
try {
const allQueries = await Promise.all([Maincategory.find().populate("subcategory").exec(), Items.find({}).exec()]),
allCategories = allQueries[0],
allItems = allQueries[1],
userId = "true",
show = "list";
res.render("test.ejs", {
allCategories: allCategories,
allItems: allItems,
userId: userId,
show: show
});
} catch (error) {
console.log("error 6");
res.render("error.ejs", {
error: error
});
};
});

Related

Not able to return value from promise in Nodejs

I have written the following code in Nodejs which is saving data in MongoDB:
function insertDoc(db,data){
return new Promise(resolve => {
callback=db.collection('AnalysisCollection').insertOne(data).then(function(response,obj){
console.log("Inserted record");
resolve(obj);
//console.log(obj);
// response.on('end',function(){
// resolve(obj);
// });
//return resolve(obj);
}).then(() => { return obj }
).catch(function(error){
throw new Error(error);
});
})
}
I am calling the above function from the main function like this:
async function cosmosDBConnect(nluResultJSON){
try{
//console.log("Inserting to cosmos DB");
console.log(nluResultJSON);
var url = config.cosmos_endpoint;
var result="";
var data = JSON.parse(JSON.stringify(nluResultJSON));
MongoClient.connect(url, function(err, client) {
assert.equal(null, err);
var db = client.db('NLUAnalysisDB');
// insertDoc(db, data, function() {
result=insertDoc(db, data, function() {
console.log(result);
client.close();
//return data._id;
});
});
}
catch (e) {
console.log(e);
}
}
module.exports = { cosmosDBConnect };
But in cosmosDBConnect, I am getting 'undefined' for the result, though in insertDoc I am getting the output for'obj' with _id for the inserted record.
Please help me to return this _id to cosmosDBConnect.
You are use callbacks inside of async function, which creates internal scopes. So your return aplies to them instead of whole function. You should use Promise-based methods inside of async function using await (without callbacks) or wrap whole function into own Promise otherwise.
Example:
function cosmosDBConnect(nluResultJSON) {
return new Promise((resolve, reject) => {
var url = config.cosmos_endpoint;
var result = '';
var data = JSON.parse(JSON.stringify(nluResultJSON));
MongoClient.connect(url, function(err, client) {
if (err) return reject(err);
assert.equal(null, err);
var db = client.db('NLUAnalysisDB');
insertDoc(db, data).then(obj => {
console.log(obj);
client.close();
return resolve(data._id);
});
});
});
}
Also you need to understand that your insertDoc return Promise and do not accept callback you tried to pass.
Ref: async function
result = insertDoc(db, data).then((data) => {
console.log(data);
}).catch(err => console.error(err));

model.update() not updating

I'm trying to add a new element to an array.
This is the code:
var findEditThenSave = function(personId, done) {
var foodToAdd = 'hamburger';
var foodArray = [];
Person.findById(personId, function (err, data) {
if (err) return console.log(err);
done(null, data);
foodArray = data.favoriteFoods;
console.log("foodArray inside findById: ", foodArray);
foodArray.push(foodToAdd);
var updateObj = {favoriteFoods: foodArray};
console.log(updateObj)
Person.update({_id: personId}, updateObj, function(err, raw) {
if (err) {
console.log("There was an error");
}
console.log("Updated successfully");
console.log("foodArray inside update function:", foodArray);
});
});
};
This is the whole code on Glitch: https://glitch.com/edit/#!/holly-maroon-pony?path=myApp.js%3A226%3A0
This is the console log for a POST request:
POST
foodArray inside findById: ["spaghetti"]
{ favoriteFoods: ["spaghetti","hamburger"] }
(node:8943) DeprecationWarning: collection.update is deprecated. Use updateOne, updateMany, or bulkWrite instead.
Updated successfully
foodArray inside update function: ["spaghetti","hamburger"]
You can use async and await while making these updates.
var findEditThenSave = async function(personId, done){
var foodToAdd = 'hamburger';
var foodArray = [];
var updateObj;
try{
var data=await Person.findById(personId);
done(null, data);
foodArray = data.favoriteFoods;
console.log("foodArray inside findById: ", foodArray);
foodArray.push(foodToAdd);
updateObj = {favoriteFoods: foodArray};
console.log(updateObj)
}catch(err){
console.log(err);
}
try{
await Person.update({_id: personId}, updateObj);
console.log("Updated successfully");
console.log("foodArray inside update function:", foodArray);
}catch(err){
console.log(err);
}
};
As you can see in your console:
(node:8943) DeprecationWarning: collection.update is
deprecated. Use updateOne, updateMany, or bulkWrite instead.
Updated successfully
So you can get through this using Person.updateOne instead of Person.update
Hope it helps
If you are just planning to update a document and return the updated document you can do something like this
function update(personId) {
var foodToAdd = "hamburger";
const person = Person.findByIdAndUpdate(
personId,
{ $push: { favoriteFoods: foodToAdd } },
{ new: true },
function (err, result) {
if (err) console.log(err);
else console.log(result);
}
);
};

node csv files upload synchronous import followed by mongoose find

I am not able to get results from a mongoose find query after successfully uploading csv files. The data from the csvs is being saved in my collection OK. But I am getting no results from a following find query. What causes promises to not conform to the sequence? I feel I may be close to understanding promises as they are handled in the event loop but this case escapes me?
let uploadfiles = function (req,res) {
return new Promise( function (resolve, reject) {
upload(req, res, (err) => {
promises = [];
if (!err) {
if (Object.keys(req.files).length>0) {
simpleLoop(req.files, function (value, key, lovey) {
promises.push(insertStreamData(req.files[key]["originalname"]));
});
console.log('hither');
}
}
return Promise.all(promises);
});
resolve('something');
});
};
// insert uploaded csv data from files into db
function insertStreamData(filename){
var originalFileName = filename;
var thefilename = originalFileName.split('.');
var csvfile = "./public/files/"+originalFileName;
var stream = fs.createReadStream(csvfile, { headers: true});
var csvStream = csv().on("data", function(data){
if(data[0] != 'Product #' && data[7] != 0){
var item = new Product({
'Invoice':thefilename[0],
'Product #': data[0],
'SKU': data[1],
'UpcCode': data[2],
'Description': data[3],
'MfgNo': data[4],
'Vendor': data[5],
'Order Qty': data[6],
'Ship Qty': data[7],
'Min Sell': data[8],
'Retail': data[9],
'Cost': data[10],
'Ext Cost': data[11],
'Box': data[12]
});
item.save(function(error){ if(error){ throw error; } });
}
}).on("end", function(){
console.log('the end');
});
stream.pipe(csvStream);
}
let getincomingproducts = function(){
return new Promise(function (resolve, reject) {
resolve('done');
});
};
router.post('/uploaddata', function(req, res) {
uploadfiles(req,res).then(function(result){
return getincomingproducts();
}).then(function(result){
console.log(result);
res.redirect('/showdata');
}).catch(err => console.log("Caught " + err));
});
Output 'done' is logged to the console prior to the first promise. I want to replace done with mongoose find results. But a simple console log demonstrates the unsynchronous result.
done
hither
the end
the end
the end
the end
Please don't mix Promise constructors and such with async and await. This could be a lot easier if you just use await and async functions. And only wrap the callback taking functions once.
A clean version wouldn't have any business logic in the Promise constructors and would used await rather than then in an async function.
I found the second point in the program where it doesn't wait for the completion of an action that is that the insertStreamData does not return a promise that is fulfilled when it is done which is what is causing the out of order issue. I will update my answer.
I see a issue in upload files where it has:
return Promise.all(promises);
});
resolve('something');
which should be
return Promise.all(promises).then(function () {
resolve('something');
});
This change should cause the output to be hither, the end * 4, done. If you want the insertStreamData to occur in serial rather than parallel, that requires a different change.
My version of the script:
function upload_wrapper(req, res) {
return new Promise(function(resolve, reject) {
upload(req, res, function (err) {
if (err) {
reject(err);
} else {
resolve();
}
});
})
}
let uploadfiles = async function(req, res) {
await upload_wrapper(req, res);
let promises = [];
if (Object.keys(req.files).length > 0) {
simpleLoop(req.files, function(value, key, lovey) {
promises.push(insertStreamData(req.files[key]["originalname"]));
});
console.log('hither');
}
await Promise.all(promises);
return 'something';
};
// insert uploaded csv data from files into db
function insertStreamData(filename) {
return new Promise(function(resolve, reject) {
var originalFileName = filename;
var thefilename = originalFileName.split('.');
var csvfile = "./public/files/" + originalFileName;
var stream = fs.createReadStream(csvfile, {
headers: true
});
var csvStream = csv();
csvStream.on("data", function(data) {
if (data[0] != 'Product #' && data[7] != 0) {
var item = new Product({
'Invoice': thefilename[0],
'Product #': data[0],
'SKU': data[1],
'UpcCode': data[2],
'Description': data[3],
'MfgNo': data[4],
'Vendor': data[5],
'Order Qty': data[6],
'Ship Qty': data[7],
'Min Sell': data[8],
'Retail': data[9],
'Cost': data[10],
'Ext Cost': data[11],
'Box': data[12]
});
item.save(function(error) {
if (error) {
//throw error;
csvStream.pause(); // here the stream should be destroyed if this stops at the first error
reject(error);
}
});
}
}).on("end", function() {
console.log('the end');
resolve('the end');
});
stream.pipe(csvStream);
});
}
let getincomingproducts = function() {
return Promise.resolve('done');
};
router.post('/uploaddata', async function(req, res) {
try {
let result = await uploadfiles(req, res);
let result = await getincomingproducts();
console.log(result);
res.redirect('/showdata');
} catch (err) {
console.log("Caught " + err)
}
});
Untested. It still could be improved.

Incorrect Output, need synchronous for loop

exports.calculateGstBaseOnInput = function(req, res) {
console.log("welcome");
for (var item of req.body.so_items) {
req.productid = item.productid;
req.qty = item.qty;
getItemDetail(req, res).then(function(result) {
return getCartItems(req, res);
}).then(function(result) {
return calculateGST(req, res);
})
}
}
getItemDetail = function(req, res) {
return new Promise(function(resolve, reject) {
console.log("inside getItemDetail");
var SQL = "mysql query";
mysqlConnect.query(SQL, function(err, result, fields) {
if (err) {
console.log("inside err");
res.json({ status: 'Failure', statusMessage: 'item does not exist' });
} else if (result.length < 0) {
console.log("inside length 0");
res.json({ status: 'Failure', statusMessage: 'item does not exist' });
} else {
req.itemdetail = result;
console.log("price inside getitemdetail= ", req.itemdetail[0].price);
//callback();
}
});
resolve('done');
});
}
getCartItems = function(req, res) {
return new Promise(function(resolve, reject) {
console.log("inside getCartItems");
var SQL = "mysql query";
mysqlConnect.query(SQL, function(err, result, fields) {
if (err) {
res.json({ status: 'Failure', statusMessage: 'item does not exist' });
} else if (result.length < 0) {
res.json({ status: 'Failure', statusMessage: 'item does not exist' });
} else {
req.cartItems = result;
//callback();
}
});
resolve('done');
});
}
calculateGST = function(req, res) {
return new Promise(function(resolve, reject) {
console.log("inside calculateGST");
if (req.userDetails[0].is_gst_included) {
//total = req.qty * req.itemdetail[0].price;
// console.log("price = ",req.itemdetail[0].price);
//callback();
} else {
//total = req.qty * req.itemdetail[0].price;
//console.log("price = ",req.itemdetail[0].price);
//total = req.qty * req.itemdetail[0].price;
}
resolve('done');
});
}
Actual output :
inside getItemDetail
inside getItemDetail
inside getCartItems
inside getCartItems
inside calculateGST
inside calculateGST
Expected Output(output i wanted):
inside getItemDetail
inside getCartItems
inside calculateGST
inside getItemDetail
inside getCartItems
inside calculateGST
how do i achieve this without setting any time.
You are resolveing immediately instead of resolving within the callback provided to mysqlConnect.query().
Consider an abbreviated version of your getItemDetail function:
function getItemDetail(req, res) {
return new Promise(function(resolve, reject) {
var SQL = "mysql query";
mysqlConnect.query(SQL, function(err, result, fields) {
// Stuff
});
resolve('done');
});
}
It's logic is:
Create a new promise
Begin a query
resolve()
Whatever called getItemDetail does what it should because getItemDetail resolved
query finishes sometime later
Instead, you should likely be doing something like this, where resolve is within the query callback:
function getItemDetail(req, res) {
return new Promise(function(resolve, reject) {
var SQL = "mysql query";
mysqlConnect.query(SQL, function(err, result, fields) {
// Stuff
resolve('done');
});
});
}
The logic here is:
Create a new promise
Begin a query
getItemDetail caller shouldn't do anything yet because getItemDetail isn't yet resolved
query finishes at some point and the callback is triggered which calls resolve
getItemDetail caller will now proceed since it has been told that the function resolved
You need to follow this pattern in any function where you need a query to actually finish before the caller should move on.
You should also consider leveraging async/await. Consider this abbreviated example using your code as a base:
const mysqlConnect = {
query(sql, cb) {
setTimeout(() => {
cb(null, ["foo"], ["bar"]);
});
}
};
(async function() {
const t = await calculateGstBaseOnInput({
body: {
so_items: ["a", "b", "c"]
}
});
}());
async function calculateGstBaseOnInput(req, res) {
for (var item of req.body.so_items) {
const itemDetail = await getItemDetail(req, res);
const cartItems = await getCartItems(req, res);
const gst = await calculateGST(req, res);
}
}
function getItemDetail(req, res) {
console.log("getItemDetail");
return new Promise(function(resolve, reject) {
var SQL = "mysql query";
mysqlConnect.query(SQL, function(err, result, fields) {
// Stuff
resolve('done');
});
});
}
function getCartItems(req, res) {
return new Promise(function(resolve, reject) {
console.log("---getCartItems");
var SQL = "mysql query";
mysqlConnect.query(SQL, function(err, result, fields) {
// Stuff
resolve('done');
});
});
}
function calculateGST(req, res) {
return new Promise(function(resolve, reject) {
console.log("------calculateGST");
// Stuff
resolve('done');
});
}
This outputs:
getItemDetail
---getCartItems
------calculateGST
getItemDetail
---getCartItems
------calculateGST
getItemDetail
---getCartItems
------calculateGST

Express js promise.all returns undefined

Hi i am newbie to express and promise, i am trying to call multiple asynchronous function using promise.all from express router, but it returns undefined, please guide me to solve the issue .
user.js //routes
var findAllUsersDetails = function(router){
router.post('/api/v1/users/getAllUserFormDetails',
function (req, res) {
Promise.all([
userModel.getAllUsers(req),
userModel.getAllUsers(req),
])
.then((data) => console.log(data))
.catch((err) => console.log(err))
});
}
user.js // models
var userModel = {
getAllUsers : function(req){
var string = "";
var id_company = req['user'].id_company;
var dbConnection = dbConnectionCreator();
var getAllUsers = getAllUsersSqlString(string, id_company);
console.log("ANGEL: finding all employees");
dbConnection.query(getAllUsers, function(error, results, fields){
return new Promise((resolve, reject) => {
console.log(results);
if (error) {
dbConnection.destroy();
console.log("error: ", error);
return reject (err);
} else if (results.length === 0) {
resolve("User not found.");
} else {
resolve(results);
//return (callback({employeeData: results}));
}
})
});
},
}
module.exports = userModel;
Your getAllUsers function is expected to return Promise, but is returning undefined(nothing).
The promise is returned to dbConnection.query but not to the getAllUsers function.
You can try adding return.
return dbConnection.query
if this doesn't work, then dbquery doesn't return the callback which was returned to it.
You might need to find an alternative to solve this.
Let me know if it works.
userModel.getAllUsers(req) should return Promise a.e.:
function getAllUsers(req) {
return new Promise(function(resolve, reject){
//...
});
}
In your case dbConnection.query(getAllUsers, function(error, results, fields) returns Promise therefore you can write something like:
getAllUsers : function(req){
var string = "";
var id_company = req['user'].id_company;
var dbConnection = dbConnectionCreator();
var getAllUsers = getAllUsersSqlString(string, id_company);
console.log("ANGEL: finding all employees");
return dbConnection.query(getAllUsers, function(error, results, fields){
// ^^^
return new Promise((resolve, reject) => {
console.log(results);
if (error) {
dbConnection.destroy();
console.log("error: ", error);
return reject (err);
} else if (results.length === 0) {
resolve("User not found.");
} else {
resolve(results);
}
})
});
},
What i did is i placed the dbconnection within promise function and now i can return promise with the results of dbConnection.query()
getAllUsers : function(req){
var string = "";
var id_company = req['user'].id_company;
var dbConnection = dbConnectionCreator();
var getAllUsers = getAllUsersSqlString(string, id_company);
console.log("ANGEL: finding all employees");
return new Promise((resolve, reject) => {
dbConnection.query(getAllUsers, function(error, results, fields){ //
console.log(results);
if (error) {
dbConnection.destroy();
console.log("error: ", error);
return reject (err);
} else if (results.length === 0) {
resolve("User not found.");
} else {
resolve(results);
//return (callback({employeeData: results}));
}
});
});
Thanks for your immediate reply, actually i understood issue is because of returning promise from your answers.

Resources