Can a node promise chain fail to execute synchronously? - node.js

If I have a sequence of promises/function as example below, is it still possible that functions would resolve out of sequence? I have one function that is a mongoose query and update then another sends the data to a view. My view is loading but is not reflecting the correct results of the mongoose query results.
let function_one(data) = function(){
return new Promise( function(resolve, reject) {
{do stuff...
resolve('howdy');
}
});
};
let function_two(some_data) = function(){
return new Promise( function(resolve, reject) {
{ do stuff...
resolve('howdy');
}
});
};
let function_three(some_data) = function(){
return new Promise( function(resolve, reject) {
{do stuff...
resolve('howdy');
}
});
};
let function_four(some_data) = function(){
return new Promise( function(resolve, reject) {
{do stuff...
resolve('howdy');
}
});
};
function_one(data).then(function(result){
return function_two(result);
}).then(function(result){
return function_three(result);
}).then(function(result){
return function_four(result);
}).then(function(result){
console.log(result);
}).catch(err => console.log("Caught " + err));
Here's the code I have put together: It is as if "res.json({status: 200, data: result});" is being executed before the mongoose find completes?
let gettheinformation = function(invoicelist3){
return new Promise( function(resolve, reject) {
// console.log(invoicelist3);
Product.aggregate([
{
$match: {
Priced: true
}
}
,
{
$group: {
_id: "$Invoice",
pricedcount: {$sum: 1}
}
}
], function (err, result) {
if (err) {
console.log(err);
} else {
resolve(result);
}
});
});
};
let getinvprodspriced = function(invlist){
return new Promise( function(resolve, reject) {
// console.log(invlist);
for(var oo = 0; oo < invlist.length; oo++){
Invoicestatus.update({Invoice:invlist[oo]._id}, {Noofitemspriced: invlist[oo].pricedcount}, {upsert: true}, function (err) {});
}
resolve(invlist);
});
};
let getinvprodcount = function(invprodcount){
return new Promise( function(resolve, reject) {
Product.aggregate([
{
$group: {
_id: "$Invoice",
pradcount: {$sum: 1}
}
}
], function (err, result) {
if (err) {
console.log(err);
} else {
// console.log(result);
resolve(result);
}
});
});
}
let saveinvprodcount = function(invprodcount){
return new Promise( function(resolve, reject) {
for(var ok = 0; ok < invprodcount.length; ok++){
Invoicestatus.update({Invoice:invprodcount[ok]._id}, {Noofitems: invprodcount[ok].pradcount}, {upsert: true}, function (err) {});
}
resolve(invprodcount);
});
};
let getarrdocs = function(result){
return new Promise( function(resolve, reject) {
Invoicestatus.find({}, function(err, docs){
resolve(docs);
});
});
};
router.get('/fetcharrdata', function(req, res) {
gettheinformation().then(function(result){
return getinvprodspriced(result);
}).then(function(result){
return getinvprodcount(result);
}).then(function(result){
return saveinvprodcount(result);
}).then( function(result){
return getarrdocs(result);
}).then(function(result){
res.json({status: 200, data: result});
}).catch(err => console.log("Caught " + err));
});

The problem is in saveinvprodcount methods for loop, function will resolve before execution of Invoicestatus.update function,
let saveinvprodcount = function(invprodcount){
return new Promise( function(resolve, reject) {
for(var ok = 0; ok < invprodcount.length; ok++){
Invoicestatus.update({Invoice:invprodcount[ok]._id}, {Noofitems: invprodcount[ok].pradcount}, {upsert: true}, function (err) {});
}
//this will resolve before completion of Invoicestatus.update
resolve(invprodcount);
});
};
First promisify update function then use an array to hold promises and You can use promise.all to execute all promises at once. See below
let saveinvprodcount = function (invprodcount) {
let promises = [];
for (var ok = 0; ok < invprodcount.length; ok++) {
promises.push(updateData(invprodcount[ok]));
}
return Promise.all(promises);
};
let updateData = function (invProd) {
return new Promise(function (resolve, reject) {
Invoicestatus.update({ Invoice: invProd._id },
{ Noofitems: invProd.pradcount }, { upsert: true }, function (err) {
if (err)
reject(err);
else
resolve();
});
});
};

Yes. You can do. by using Promise.all([promise1, promise2, promise3]) to let them running asynchronously and get the result at once. However, in this case, the result of the first call is the input, you need to wait for it before running the second one. So that, you have to run in sequence (because of the business logic, not the nature of the language.

Related

promise all not working as expected while working with list

I want the callback to return the response when promise all finishes. I am getting the response before promise all. Batch List is empty in response, as it is returned before promise all.
getByReferenceID(object, mode, limit, lastEvaluatedKey, callback){
var results = {};
var requestList = [];
var batchList = [];
var response = {};
new Promise((resolve, reject) => {
this.getRequestList(object, limit, lastEvaluatedKey,function (err, result) {
console.log(result);
results = result;
if(err) {
reject();
} else {
resolve();
}
});
}).then(async () =>
{
requestList = requestList.concat(results.items);
const runAsyncFunctions = async () => {
var promises = [];
requestList.map(async request => {
var promise = await this.getBatchList(request.requestID.S, mode, null, null, function(err, result) {
batchList = batchList.concat(result.items);
});
promises.concat(promise);
});
await Promise.all(
promises
).then(()=>{
response = {
"requests": requestList,
"batches": batchList,
"lastEvaluatedKey": results.LastEvaluatedKey
};
callback("", response);
}).catch((error) => {
console.log(error);
});
};
await runAsyncFunctions();
}).catch((error) => {
callback(error, response);
});
}
promises is not an array of promises.
The line
var promise = await this.getBatchList(request.requestID.S, mode, null, null, function(err, result) {
batchList = batchList.concat(result.items);
});
is executed later then
callback("", response)
I guess you want to do something like this
var promises = [];
for (const request of requestList) {
const promise = this.getBatchList(request.requestID.S, mode, null, null, function (err, result) {
batchList = batchList.concat(result.items);
});
promises.push(promise);
}
await Promise.all(
...
The correct way of doing it is:
getByReferenceID(object, mode, limit, lastEvaluatedKey, callback){
var results = {};
var requestList = [];
var batchList = [];
var response = {};
new Promise((resolve, reject) => {
this.getRequestList(object, limit, lastEvaluatedKey,function (err, result) {
console.log(result);
results = result;
if(err){
reject(err);
}
else{
resolve();
}
});
return results;
}).then(() =>
{
requestList = requestList.concat(results.items);
var iteration =0;
new Promise((resolve, reject) => {
for (let request of requestList) {
console.log(request.requestID.S);
this.getBatchList(request.requestID.S, mode, null, null, function (err, result) {
iteration++;
batchList = batchList.concat(result.items);
if(iteration === requestList.length)
{
resolve();
}
if(err)
{
reject();
}
});
}
}).then(()=>{
response = {
"requests": requestList,
"batches": batchList,
"lastEvaluatedKey": results.LastEvaluatedKey,
};
callback("", response);
}).catch((error) => {
callback(error, response);
});
}).catch((error) => {
callback(error, response);
});
}

Return value from promise rather than console

How can we return a value from promise rather than console.log? If i change console to return(values[1] - values[0]). And try to do console.log(balance(user)), it prints Promise { <pending> }.
I don't know if i am doing it right or not, what am i trying to do is get balance of user by subtracting all purchases from all Deposit/withdrawals. Doing without promises, it returns value before the DB queries are completed. Any better solution?
function totalPurchases(user) {
return new Promise(function (resolve, reject) {
Purchase.getTotalPurchases(user, function (err, tot) {
if (err) {
// console.log(err);
reject(new Error("Error: 1002"));
// return false;
}
else {
resolve(tot[0].purchases);
}
})
})
}
function totalDW(user) {
return new Promise(function (resolve, reject) {
DepWid.getTotalDepwids(user, function (err, tot) {
if (err) {
reject(new Error("Error: 1003"));
}
else {
resolve(tot[0].depWids);
}
})
})
}
exports.balance = async (user) => {
let promises = [];
promises[0] = totalPurchases(user);
promises[1] = totalDW(user);
Promise.all(promises)
.then(function (values) {
console.log(values[1] - values[0]);
//Return this rather than console.log
})
.catch(function (err) {
console.log(err);
})
}
Since balance is an async function, you can await Promise.all and then later return the result.
function totalPurchases(user) {
return new Promise(function (resolve, reject) {
setTimeout(function(){
resolve(5);
}, 2000);
})
}
function totalDW(user) {
return new Promise(function (resolve, reject) {
setTimeout(function(){
resolve(3);
}, 1000);
});
}
let balance = async (user) => {
let promises = [];
promises[0] = totalPurchases(user);
promises[1] = totalDW(user);
const [deposits, withdrawals] = await Promise.all(promises);
return deposits - withdrawals;
}
balance().then(r => { //If `balance' is called from an async method you can do await here. Else you need to do `then` like I have done here
console.log(r); //prints 2
});

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

resolving a promise using mongodb and nodejs

Hello I am new to nodejs and mongodb, i am having trouble resolving my first promise after the second promise has been resolved. I can see the result of my second promise after it is resolved. Here is my code
var getShows = function () {
return new Promise(function (resolve, reject) {
usersdb.find(function (err, result) {
if(err) return console.error(err);
var usersFromCall = result;
var task = function (users) {
return new Promise(function (resolve, reject) {
var user = {
'name': '',
'pages': []
}
user.name = users.show;
console.log(users);
pagedb.find({'show' : user.name}, function (err, resp) {
for(var j = 0 ; j <resp.length; j ++){
var pages = { 'pageId': ''};
pages.pageId = resp[j].pageId;
user.pages.push(pages);
}
console.log(user);
resolve({show: user});
})
});
};
var actions = usersFromCall.map(task);
return Promise.all(actions);
}).then(function () {
resolve()
})
});
};
do i resolve the first promise in a then function after the find?
The following should work:
var getShows = function () {
return new Promise(function (resolve, reject) {
usersdb.find(function (err, users) {
if (err) return console.error(err);
var task = function (user) {
return new Promise(/* ... */);
};
var actions = users.map(task);
Promise.all(actions).then(resolve, reject);
});
});
};
getShows().then(function (results) {
// Prints the result of each task as an array
console.log(results);
});
Looking at your code, it seems .find returns a Promise. So, just for the sake of avoiding the Promise constructor anti-pattern please try the following, I believe it will produce the correct result
var getShows = function() {
return usersdb.find()
.then(result =>
Promise.all(result.map(
users =>
pagedb.find({
show: users.show
})
.then(resp => ({
show: {
name: users.show,
pages: resp.map(item => ({pageId: item.pageId}))
}
}))
)
)
);
};
or the ES5 version
var getShows = function getShows() {
return usersdb.find().then(function (result) {
return Promise.all(result.map(function (users) {
return pagedb.find({
show: users.show
}).then(function (resp) {
return {
show: {
name: users.show,
pages: resp.map(function (item) {
return { pageId: item.pageId };
})
}
};
});
}));
});
};

Node js send an email with pdf as attachement synchronously

Task : Get query from database => generate multiple pdf => send in mail attachment via node js
Problem: the problem here is before pdfs are generated my sendMail() function called. So how to call sendMail function only after all pdfs generated?
pdfs = [];
// Pdf generate code
var generatePdf = function (rows) {
return new Promise(function (resolve, reject) {
for (var i = 0; i < rows.length; i++) {
html = ejs.renderFile(
'views/voucher.ejs',
{
voucher: rows[i]
},
function (error, success) {
if (error) {
console.log(error);
} else {
var pdf_path = '/srv/ voucher/uploads/voucher/' + Math.random().toString(36).substring(7) + '.pdf';
htmlToPdf.convertHTMLString(success, pdf_path, function (error, success) {
if (error) {
console.log('Oh noes! Errorz!');
console.log(error);
} else {
pdfs.push(pdf_path);
console.log("Single");
console.log(pdfs);
console.log('Woot! Success!');
}
});
}
});
}
resolve();
});
}
// send mail code
var sendMail = function () {
new Promise(function (resolve, reject) {
console.log("All" + pdfs);
pdfs.forEach(function (value, key) {
// mail sending code
});
});
};
var findVoucherAndSendMail = function () {
return new Promise(function (resolve, reject) {
var query = con.query('SELECT * FROM voucher limit 20', function selectAll(err, rows, fields) {
if (err) {
throw err;
}
resolve(rows);
});
});
};
findVoucherAndSendMail().then(function (fromResolve) {
return generatePdf(fromResolve);
}).then(function () {
return sendMail();
}).catch(function () {
});
Your problem is link to an asynchronous behavior in your for loop when generating pdf.
Your promise is resolved before your pdfs are generated.
You should use Promise.all to wait for generation completion.
function generatePdf(rows) {
return Promise.all(rows.map(row => asyncPdfGeneration(row));
}
function asyncPdfGeneration(row) {
return new Promise(function (resolve, reject) {
...generate pdf
resolve(pdf);
}
}
The 2nd 'then' is on your function findVoucherAndSendMail(), so both generatePdf() and sendMail() will fire once findVoucherAndSendMail() is finished.
Place the 2nd 'then' on generatePdf()
findVoucherAndSendMail().then(function (fromResolve) {
return generatePdf(fromResolve).then(function () {
return sendMail();
});
}).catch(function () {
});
edit
Did you try: resolve(pdfs); instead of just resolve();

Resources