Nodejs not returning promise properly - node.js

I'm working on a NodeJS + Express + Mongoose + MongoDB project.
This code works fine:
var findRecord = function(id) {
// find record by id
return User.findOne({
_id: id
}).then(function(record) {
// if record not found
if (!record) {
return Promise.reject(constant.NOT_FOUND);
} else {
// return found object
return Promise.resolve({
data: record,
status: constant.READ_SUCCESS
});
}
}, function() {
// if there is error reading record
return Promise.reject(constant.READ_ERROR);
});
};
Don't know what is wrong with this one:
var allRecords = function(skip, limit) {
// find all records
return User.find({}).limit(limit).skip(skip)
.then(function(records) {
console.log('executed');
// if records found
return Promise.resolve({
data: records,
status: constant.READ_SUCCESS,
});
}, function() {
return Promise.reject(constant.READ_ERROR);
});
};
executed never prints on console
Even this one also don't work:
var allRecords = function(skip, limit) {
// find all records
return User.find({}).limit(limit).skip(skip)
.exec(function(records) {
console.log('executed');
// if records found
return Promise.resolve({
data: records,
status: constant.READ_SUCCESS,
});
}, function() {
return Promise.reject(constant.READ_ERROR);
});
};

You need to run exec method:
User.find({}).limit(limit).skip(skip).exec();
Besides of this you shouldn't use Promise.resolve to return result from a promise, just return an object. Also, it's better to use catch callback, instead of second callback of then:
var allRecords = function(skip, limit) {
return User.find({})
.limit(limit)
.skip(skip)
.exec()
.then(records => {
console.log('executed');
// if records found
return {
data: records,
status: constant.READ_SUCCESS,
};
})
.catch(err => Promise.reject(constant.READ_ERROR));
};

Related

Promise never settles - NodeJS

I've made the following loop, where I loop an Array; and then doing a quick GET request for each of them.
I made the loop work and the data is logged correctly. However the "loop" never finishes, and therefore not reaching ().then => {}
Promise.all(
arr.map((key,item) => {
return new Promise((resolve,reject) => {
var costmicro = 0;
request.post({
url: 'https://googleads.googleapis.com/v8/customers/'+arr[item]+'/googleAds:searchStream',
headers: {
'Content-Type': 'application/json',
},
json: {
"query": "SELECT metrics.cost_micros FROM campaign WHERE segments.date DURING LAST_30_DAYS",
}
}, function (errAdsCampaign, httpResponseAdsCampaign, bodyAdsCampaign) {
if (!errAdsCampaign) {
for (ii in bodyAdsCampaign[0].results) {
costmicro = costmicro+parseInt(bodyAdsCampaign[0].results[ii].metrics['costMicros']);
var arrlength = bodyAdsCampaign[0].results.length-1;
if (ii == arrlength) {
objectArray.push({name: arr[item], cost: costmicro / 1000000});
resolve(objectArray);
}
}
} else {
reject();
}
});
});
})
).then(()=>{
console.log('done');
console.log(objectArray)
}).catch(() => {
console.error;
});
UPDATE
Replaced the catch() function. Like so; sadly it never returns an error, and never finished
}).catch((e) => {
console.error(e);
});
Any help is much appreciated!
Inside your function inside the loop, you need to account for all possible exit paths and call either resolve() or reject(). One such exit path that is not handled is if bodyAdsCampaign[0].results is an empty array or undefined. In this case, the function will finish running without calling resolve() or reject().
You can also add a call to reject() at the end of your function (errAdsCampaign, httpResponseAdsCampaign, bodyAdsCampaign) callback function to catch all scenarios which aren't handled within the function.
Your code inside the loop likely threw an error and it skipped the .then() clause and went to the .catch() clause. You can replace the .catch() clause with the code below to see what error was encountered.
}).catch((e) => {
console.error(e);
});
The library request has been deprecated. Ref. https://www.npmjs.com/package/request
Instead, I would suggest using axios.
(async function run() {
try {
const objectArray = await Promise.all(
arr.map(async (key, item) => {
const res = await axios.post(
"https://googleads.googleapis.com/v8/customers/" +
arr[item] +
"/googleAds:searchStream",
{
json: {
query:
"SELECT metrics.cost_micros FROM campaign WHERE segments.date DURING LAST_30_DAYS",
},
},
{
headers: {
'Content-Type': 'application/json',,
},
}
);
const costmicro = res.data.bodyAdsCampaign.results.reduce((acc, cur) => {
acc += cur.metrics["costMicros"];
return acc;
}, 0);
return { name: arr[item], cost: costmicro / 1000000 };
})
);
console.log(objectArray);
} catch (err) {
console.log(err);
}
})();

NodeJS / Express Why am I getting "Cannot Read Property 'then' of Undefined"?

I apologize in advance because I have seen other questions on this very topic, but I do not understand what I am doing wrong and how to fix it. Please have a look at my code:
function getSum(productID) {
Rating.aggregate(
[
{
$group: {
_id: "$productID",
total: {
$sum: "$rating"
}
}
}
],
function (err, result) {
if (err) {
res.send(err);
} else {
//console.log("product-sum: " + req.body.productID)
const count = result.find(item => item._id === productID.productID);
console.log("getSum count: ", count.total);
return count.total;
}
}
);
}
router.route('/compute-rating').post((req, res) => {
console.log("compute Rating: ", req.body.data);
var productID = req.body.data;
var sum = getSum(productID).then( //getting the error here
res.json({ sum })
);
});
getSum() returns a valid number from count.total.
Once I get the sum, I plan to chain another .then onto the existing then and call another function with the productID, but I need to use the sum later in the computer-rating route.
In the code, I have a comment that shows where the error, "Cannot Read Property 'then' of Undefined", is occurring. Why am I getting this error and how can I fix it?
Thanks.
Edit:
I wanted to show my final solution so others could benefit from my experience. Hopefully I did not create any major Javascript violations in my code. I ended up using Promise.all because I had to perform a calculation based on 2 returned values.
function getSum(productID) {
return new Promise(async (resolve, reject) => {
const result = await Rating.aggregate( //sum of a column in MongoDB
[
{
$group: {
_id: "$productID",
total: {
$sum: "$rating"
}
}
}
]
);
try {
var sum = result.find(item => item._id === productID.productID);
if (sum !== undefined) {
resolve(sum);
console.log("getSum sum: ", sum);
}
else {
reject("invalid product id");
}
}
catch (e) {
reject(e);
}
});
}
function getCount(productID) {
return new Promise(async (resolve, reject) => {
var result = await Rating.countDocuments(productID)
.then(count => {
console.log("getCount count:", result);
var documentCount = { count: count }
resolve(documentCount);
})
.catch(err => reject(err));
});
}
router.route('/compute-rating').post((req, res) => {
console.log("compute Rating: ", req.body.data);
var productID = req.body.data;
Promise.all([getSum(productID), getCount(productID)])
.then(results => {
console.log("compute rating results: ", results);
if (results[1].count > 0) {
res.status(200).json({ rating: results[0].total / results[1].count });
}
else {
res.status(200).json({ rating: 0 });
}
})
.catch(err => {
res.status(400).json({ error: err });
})
});
Your getSum doesn't return anything. You are returning only from the callback function, not the getSum function.
You should make it async.
And also you are doing res.send in that function, while not having access to res object.
For example you can do it like this:
async function getSum(productID) {
const result = await Rating.aggregate(
[
{
$group: {
_id: "$productID",
total: {
$sum: "$rating"
}
}
}
]
);
const count = result.find(item => item._id === productID.productID);
console.log("getSum count: ", count.total);
return count.total;
}
router.route('/compute-rating').post((req, res) => {
console.log("compute Rating: ", req.body.data);
var productID = req.body.data;
// EDIT: This should work
getSum(productID).then(sum => {
res.json({ sum })
});
});
then only works on functions that return a promise. your function getSum is not returning anything, if Rating.aggregate function returns a promise, aka accepts a then, then you should return this aggregate, simply add return before calling it.
Now if aggregate doesn't return a promise, and I'm guessing so because you're passing a callback function to it, you might want to return a promise that resolves using promise.resolve in this callback body.

How to handle async when making mongoose query in each array element in express?

On the following post method, I'm having some issues due to moongose async. res.send(suggestions) is executed first then Expense.findOne.exec
app.post('/suggestions', async function(req, res) {
const suggestions = await req.body.map((description) => {
Expense.findOne({ description: new RegExp(description, 'i') }).exec((err, result) => {
if (result) {
console.log(result.newDescription);
return {
description,
newDescription: result.newDescription,
category: result.category,
subcategory: result.subcategory
};
}
});
});
res.send(suggestions);
});
The result is a array of null values. How can I executed a query for each item, then execute res.send(suggestion)?
Found solution with the following code:
app.post('/suggestions', async function(req, res) {
try {
if (req.body.length > 0) {
const suggestions = req.body.map((description) =>
Expense.findOne({ description: new RegExp(description, 'i') })
);
const results = await Promise.all(suggestions);
return res.send(results);
}
} catch (e) {
console.log('error', e);
}
});

How to refactor promises all chain using async await Node js and sequelize ORM

I want to refactor code for chain of promises by async, await. I have sequelize ORM for DB management and the code is written in AWS Lambda function having multiple middleware. In such cases I have to traverse code for multiple entries using sequelize transactions. It is easy to manage using promise.all() but need to change it to async await syntax for cleaner code.
Here are my demo code.
/* get all invoice where user_id === current logged in user, and where status != "paid" */
db.Invoice.findAll({
where: {
user_id: currentLoggedInUser,
status: {
$ne: "paid"
}
}
}).then(invoices => {
if (!invoices || invoices === null) {
return false;
}
function addScheduledTransactionAttempts(invoice, tryPayOnDate, t) {
return new Promise((resolve, reject) => {
/* check If any ScheduledTransactionAttempts exists for this invoice.id */
db.ScheduledTransactionAttempts.find({
where: {
invoice_id: invoice.id
}
})
.then(function(attempts) {
if (attempts) {
attempts
.destroy({}, {
transaction: t
})
.then(deletedAttempts => {
console.log("Attempts Record Deleted: ", deletedAttempts);
})
.catch(error => {
reject(error);
t.rollback();
});
}
return db.ScheduledTransactionAttempts.create({
invoice_id: invoice.id,
payment_source_id: PaymentMethodId,
try_pay_on_date: tryPayOnDate,
stripe_customer_id: currentLogInStripeCustomerId
}, {
transaction: t
})
.then(function(attempt) {
resolve(attempt.id);
})
.catch(error => {
reject(error);
t.rollback();
});
})
.catch(error => {
reject(error);
t.rollback();
});
});
}
//Run transaction to addScheduledTransactionAttempts
return db.sequelize.transaction().then(function(t) {
let promiseArr = [];
var i = 0;
invoices.forEach(function(invoice) {
var schedulePaymentDate = moment(paymentDate);
if (invoice) {
let tryPayOnDate = schedulePaymentDate
.add(i, "month")
.format("YYYY-MM-DD");
promiseArr.push(
addScheduledTransactionAttempts(invoice, tryPayOnDate, t) //calling above function
);
i++;
}
});
//now execute promise all
Promise.all(promiseArr)
.then(function(result) {
t.commit();
return true;
})
.catch(function(err) {
t.rollback();
return false;
});
});
});
In the above code I want to change
Promise.all(promiseArr)
which is calling
addScheduledTransactionAttempts
function to do DB queries to simple async function await process to make it easy simpler understandable without having multiple .then or .then inside then promises.
Any help regarding would be appreciated,
Thanks.
It's quite simple. await is valid when invoking methods that return a Promise. All of your SDK methods already return a promise, so refactoring should be quite straight forward. Here's something to get you off ground:
const processInvoices = async currentLoggedInUser {
const invoices = await db.Invoice.findAll({
where: {
user_id: currentLoggedInUser,
status: {
$ne: 'paid',
},
},
});
if (yourOwnLogicForInvoicesObject) {
for (const invoice of invoices) {
const potentiallyFoundInvoice = await db.ScheduledTransactionAttempts.find({
where: {
invoice_id: invoice.id,
},
});
if (potentiallyFoundInvoice) {
await addScheduledTransactionAttempts(potentiallyFoundInvoice)
}
}
}
}
const addScheduledTransactionAttempts = async invoice => {
console.log('Do something with your invoice', invoice)
}
Long story short: refactor the code inside your functions into smaller functions and make these new functions async, just like I did with addScheduledTransactionAttempts and processInvoices
More on async/await

How can I stack request calls so they don't complete until one is finished?

I have an array of items that I need to post to my server. I've tried the following but i never iterates.
var i = 0;
while (i < numOfItems) {
var item = items[i];
var a;
for(var ik in item){
console.log(item[ik]);
a = item[ik]; // Gets the key
break;
}
var formData = {
ID : ID,
UID : UID,
item : a
}
request.post({url:'http://example.com/a', formData: formData}, function(err, httpResponse, body){
if (err) {
return console.error('Post failed:', err);
}
console.log('Post successful! Server responded with:', body);
i++;
});
}
Your code won't work because request.post is asynchronous. If your objective is to make a call for each element in the array, a working and a more elegant solution would be to use Promises.all().
Here's your code modified with Promises —
function postRequest(url, formData) {
return new Promise((resolve, reject) => {
request.post({ url, formData }, function (err, httpResponse, body) {
if (!error) {
resolve({ message: 'Post successful!', response: body });
} else {
reject(err);
}
});
})
}
// Map your data array to an array of Promises
let promises = yourArray.map(element => {
let formData = {
ID: ID,
UID: UID,
item: element
}
return postRequest({ url: 'http://example.com/a', formData: formData })
});
// Wait for all Promises to complete
Promise.all(promises)
.then(results => {
// Handle results
})
.catch(e => {
// Handle error
});
A few things to note -
I'm reusing the fields ID and UID as-is, as it isn't clear where they come from in your code.
Replace yourArray with the array containing your data items.

Resources