I want to send an order-confirmation email(with nodemailer) to customers when they finished the order.
In this confirmation email I want to insert some data from the buyed products.
I've used many async/await combinations but none of them succeeded.
The code I got for now:
async function getProductData(products) {
let productsArray = products;
try {
let productsHtml = "";
for await (const product of productsArray) { // loop through the products
db.collection("collectionname").findOne(query, (err, data) => { // get data for every product
if (err) console.log(err)
else {
let productHtml = `<p>productname: ${data.productname}</p>
<img src="${data.imageSrc}">`;
productsHtml += productHtml; // add the product html string to productsHtml
}
})
}
} catch (error) {
console.log(error);
}
return productsHtml; //return the productsHtml string. But it returns "", because it doesn't await the mongodb function
}
async function sendConfirmationMail() {
let productsHtml = await getProductData(products); //"products" is an array
//nodemailer code
let transporter = nodemailer.createTransport({
// configuration
});
let email = await transporter.sendMail({
//email information
html: `<p>Thank you for your order!</p>
${productsHtml}`;, // insert the html string for all products into the mail
});
}
The problem is that the return statement in getProductData() fires before the for loop finishes. And then returns undefined.
Does anyone have tips on how I can implement async/await the correct way here?
You are not awaiting your database call. You are passing it a callback, so it does not return a Promise :
.findOne(query, (err, data) => {
This has a callback. You are passing it a function as second argument, that takes (err,data) as its own arguments. You need to remove this callback and use the async/await syntax with try/catch. Something like
const data = await db.collection("collectionname").findOne(query);
On the other hand you don't need to for await (const product of productsArray). You are iterating an array of static values, not an array of Promises. Remove this await.
Related
This question already has answers here:
How to return many Promises and wait for them all before doing other stuff
(6 answers)
Closed 11 months ago.
router.get("/timeline/:userId", async (req, res) => {
try {
//creating friend posts object
const friendPosts = await Promise.all(
currentUser.followings.map((friendId) => {
return Post.find({ userId: friendId });
})
);
var newFriendPosts = [];
friendPosts.map(async (friend) => {
friend.map(async (post) => {
const { profilePicture, username } = await User.findById(post.userId);
const newFriendPostsObject = {
...post.toObject(),
profilePicture,
username,
};
console.log(newFriendPostsObject);
newFriendPosts.push(newFriendPostsObject);
});
});
console.log(newFriendPosts);
// console.log(newUserPosts);
res.status(200).json(newUserPosts.concat(...newFriendPosts));
} catch (err) {
res.status(500).json(err);
}
});
So the object value is coming correct in console.log(newFriendPostsObject) but when I push that in the array newFriendPosts = []; it shows empty array in console.log(newFriendPosts);
Below is where I am getting user details from DB (DB is in MongoDB):-
const { profilePicture, username } = await User.findById(post.userId);
.map() is NOT async aware. So, it just marches on and returns an array of promises. Meanwhile, your code never waits for those promises so you're trying to use newFriendPosts BEFORE any of your .push() operations have run. .push() is working just fine - you're just trying to use the array before anything has actually gotten around to calling .push(). If you add appropriate console.log() statements, you will see that the newFriendPosts.push(...) occurs after your console.log(newFriendPosts);.
The simplest fix is to change both .map() statements to a regular for loop because a for loop IS async aware and will pause the loop for the await.
router.get("/timeline/:userId", async (req, res) => {
try {
//creating friend posts object
const friendPosts = await Promise.all(
currentUser.followings.map((friendId) => {
return Post.find({ userId: friendId });
})
);
const newFriendPosts = [];
for (let friend of friendPosts) {
for (let post of friend) {
const { profilePicture, username } = await User.findById(post.userId);
const newFriendPostsObject = {
...post.toObject(),
profilePicture,
username,
};
newFriendPosts.push(newFriendPostsObject);
}
}
console.log(newFriendPosts);
res.status(200).json(newUserPosts.concat(...newFriendPosts));
} catch (err) {
res.status(500).json(err);
}
});
One mystery in this code is that newUserPosts does not appear to be defined anywhere.
I have a list of IDs to query Mongo with which I am doing in a for loop and it always returns an empty []. Initially it kept returning promises so I moved from forEach to the standard for loop as you see here and toArray() both of which resulted in an empty [].
async function queryRoles(id) {
const db = await connectToMongo()
let response;
try {
response = await db.collection("permissions").find({"_id": xxx}).toArray();
console.log(await response);
} catch (error) {
console.log(error);
}
return response;
}
async function checkAuthorisation(list, groups) {
for (const item of list) {
const index = list.indexOf(item);
const rolesList = await queryRoles(item._id);
console.log(rolesList);
};
}
The rolesList always comes back in [] and is never populated with anything.
However, if I just update code with the same query for a single mongo document without using the for loop, the expected response comes back, so I know the connectivity to MongoDB is fine.
What am I doing wrong? Pulling my hair out at this point!!
Did you try this approach,
async function checkAuthorisation(list, groups) {
const roleList = await Promise.all(
list.map(async (item, index) =>
await queryRoles(item._id)
)
);
...
}
i'm trying to create an array which should have an items with open quotation. i have an Items DB in Mongo with key "open_quotation", if there is no quotation for this item => open_quotation: null. i want to map all the array items and to check if there is any open quotations for this item, if there are an open quotation - i want to push it to another array, whihc I created, and to send to client. the problem that when i do map method on items array it is an async function and i get an array already after that the response has been send... so the client receive it as an empty array. pls help to solve this problem.
const{items}= req.body;
const quotations=[];
items.map(async item=>{ const i=await Item.findOne({_id: item._id})
if(i){
`if(i.openquotation){ quotations.push(i)}`
return
)}
res.send({response: true, quotations:quotations})
`````
Can you try that:
try {
const response = await Promise.all(items.map(item => Item.findOne({_id: item._id})));
const quotations = response.filter((elem) => !!elem.openquotation);
res.send({ response: true, quotations });
} catch (error) {
res.send({ response: false, error });
}
you can use async and q module for find query in loop
when you run mainFunction(), mainFunction calls getData()
let async = require('async');
let q = require('q');
const mainFunctio = async()=>{
const{items}= req.body
console.log("start")
resultFromFindLoob = await getData(items)
console.log("finish");
console.log(resultFromFindLoob)
}
const getData = async (items)=>{
let defer =q.defer();
let result = []
async.eachSeries(items , async(item)=>{ // like a for
try {
let i = await Item.findOne({_id: item._id}).lean();
//do something for result (processing)
if(i)
result.push(i)
} catch (error) {
console.log(error)
}
},()=>{ //callback when finish loop
console.log("finish findone() loop")
defer.resolve(result)//return the result
})
return defer.promise
}
mainFunctio()
restult of mainFunction() based on console.log
1.start mainFunction
2.start getData
3.finish find loop getData
4.finish mainFunction
5.final result : .....
So I have a post route which is an async await function, and I have a reuest inside it which pulls some data from the api and I want to save the content of that body in a variable outside the request function
I have tried using promises but I am not really familiar with that.
//#route POST api/portfolio/stock
//#desc Create or update a user's stock portfolio
//#access private
router.post(
"/stock",
auth,
[
check("symbol", "symbol is require or incorrect.")
.not()
.isEmpty(),
check("qty", "Quantity of the stock purchased is required")
.not()
.isEmpty()
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty) {
return res.status(400).json({ errors: errors.array() });
}
const { stock, qty } = req.body;
const newPortfolio = {};
newPortfolio.user = req.user.id;
newPortfolio.stocks = [];
if(stock) newPortfolio.stocks.stock = stock;
if(qty) newPortfolio.stocks.qty = qty;
request(`https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=${stock}&apikey=${config.get(
"API_KEY")}`, (error, response, body) => {
if (error) console.error(error);
if (response.statusCode !== 200) {
res.status(404).json({msg: 'No stock found'});
}
let content = JSON.parse(body);
let quote = content['Global Quote'];
});
newPortfolio.stocks.stockInfo = quote;
try {
let portfolio = await Portfolio.findOne({ user: user.req.id });
//update if exists
if(portfolio) {
portfolio = await Portfolio.findOneAndUpdate(
{ user: user.req.id },
{ $push: { stocks: newPortfolio.stocks }}
);
return res.json(portfolio);
}
//create if not found
portfolio = new Portfolio(newPortfolio);
await portfolio.save();
res.json(portfolio);
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error");
}
}
);
I want to save myPortfolio.stocks.stockInfo using body of that request.
How to access the body of a request outside in an async function?
You don't. Asynchronous results are only available in the asynchronous context. For an asynchronous function that returns the result via a callback, that means you consume/use the results INSIDE the callback. Any code that needs access to those results goes in the callback. In traditional async Javascript programming, you just continue the code of your function inside the callback.
Fortunately, promises and the invention of async and await can make the coding a bit simpler. For asynchronous functions that return a promise (instead of taking a callback), you can use await to get their result and you can write code that looks more like the sequential model, even though it's still asynchronous.
For example, this is what a rewrite of your function might look like where we switch the request-promise library (same as the request library, but returns a promise instead of uses a callback) and then we use await on the result:
const rp = require('request-promise');
router.post(
"/stock",
auth,
[
check("symbol", "symbol is require or incorrect.")
.not()
.isEmpty(),
check("qty", "Quantity of the stock purchased is required")
.not()
.isEmpty()
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty) {
return res.status(400).json({
errors: errors.array()
});
}
const {
stock,
qty
} = req.body;
const newPortfolio = {};
newPortfolio.user = req.user.id;
newPortfolio.stocks = [];
if (stock) newPortfolio.stocks.stock = stock;
if (qty) newPortfolio.stocks.qty = qty;
try {
let body = await rp(`https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=${stock}&apikey=${config.get("API_KEY")}`);
let content = JSON.parse(body);
newPortfolio.stocks.stockInfo = content['Global Quote'];
} catch (e) {
console.error(e);
res.status(404).json({
msg: 'No stock found'
});
return;
}
try {
let portfolio = await Portfolio.findOne({
user: user.req.id
});
//update if exists
if (portfolio) {
portfolio = await Portfolio.findOneAndUpdate({
user: user.req.id
}, {
$push: {
stocks: newPortfolio.stocks
}
});
return res.json(portfolio);
}
//create if not found
portfolio = new Portfolio(newPortfolio);
await portfolio.save();
res.json(portfolio);
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error");
}
}
);
Note: your adding properties to newPortfolio.stocks which is an array is quite unusual. While it's not technically wrong, you would usually declare newPortfolio.stocks to be an object, not an array if you were just going to add properties to it and not use it like an actual array. It can often be confusing to people reading your code if you use the same variable as both an object and an array. Usually, you want a variable (or property) to behave as one or the other, not both.
I have a simple custom module in which all methods are supposed to return database records.
I am getting all records after running a query but when I try to assign those records to some variable then it says null. Not sure what is going on.
Here is my custom module code:
module.exports = {
mydata: {},
all: function (req, res, next) {
var posts = null;
con.query("SELECT post_id,title, body from `posts`", function (err, rows, fileds) {
if (err) {
console.log('Error' + err);
}
posts = rows[0]; // assigned object object to posts but not working
console.log('names' + posts);
});
console.log('my posts' + posts); // it says null/empty
return posts; // empty return
},
I am calling all methods like this in my route:
console.log("admin.js :" + postModel.all());
All are empty or null. Please guide or suggest. Am I missing anything?
Try using async & await plus it's best practice to wrap the code in try catch, any pending promise will be resolved at the .then method or any error will be caught at the .catch of the caller/final function:
/ * Handler (or) Service (or) Whatever */
const FetchDataFromDB = require('../');
/* caller Function */
let fetchDataFromDB = new FetchDataFromDB();
fetchDataFromDB.getDataFromDB()
.then((res) => console.log(res))
.catch((err) => console.log(err))
/* DB Layer */
class FetchDataFromDB {
/* Method to get data from DB */
async getDataFromDB() {
const getQuery = "SELECT post_id,title, body from `posts`";
try {
let dbResp = await con.query(getQuery);
if (dbResp.length) {
//Do some logic on dbResp
}
return dbResp;
} catch (error) {
throw (error);
}
}
}
module.exports = FetchDataFromDB;
Welcome my friend to the world of Asynchronous function.
In your code the
console.log('my posts' + posts); and return posts;
are executing before the callback assigns values to the posts variable.
Also restrict yourself from using var instead use let for declaring variable works better without error for scoped functions.
Here below:
The async keyword declares that the function is asynchronous.
The await keyword basically says that lets first get the result and then move on to next statement/line. All awaits should be done inside an async functions only.
module.exports = {
mydata: {},
all: async function (req, res, next) { //Let this be an asynchronous function
try{
let [rows, fields] = await con.query("SELECT post_id,title, body from `posts`");
//let us await for the query result so stay here until there is result
let posts = rows[0]; // assign first value of row to posts variable now
console.log('my posts' + posts);
return posts;
}catch(err){
return err;
}
},
}
Please take time reading up nature of Asynchronous, Non-blocking in JavaScript and how to handle them with Promises or Async/await (my personal choice).