Mongoose exec() does not await properly - node.js

For some reason, exec() is not awaiting in my code:
let team = <SOME TEAM NAME> //putting manually for testing
let results = []
TeamModel.find({ name: team })
.exec(async (err, docs) => {
if (err)
return res.send(Response("failure", "Error occured while retrieving fixtures"))
for(let i = 0; i < docs.length; i++){
let doesExist = await FixtureModel.exists({ leagueName: docs[i].leagueName })
if (doesExist) {
let query = FixtureModel.find({ leagueName: docs[i].leagueName, $or: [{ homeTeam: team }, { awayTeam: team }] })
await query.exec((err2, docs2) => {
if (err2)
return res.send(Response("failure", "Error occured while retrieving fixtures"))
docs2.forEach((doc2, index) => {results.push(doc2.toObject())})
console.log('during await') //Executes second
})
console.log('after await') //Executes first
}
else { //This section is not required
let result = await Communicator.GetFixturesFromLeague(docs[i].leagueId)
result.api.fixtures.forEach((fixture, index) => {
let newFixture = new FixtureModel({
fixtureId: fixture.fixture_id,
leagueId: fixture.league_id,
leagueName: fixture.league.name,
eventDate: fixture.event_date,
statusShort: fixture.statusShort,
homeTeam: fixture.homeTeam.team_name,
awayTeam: fixture.awayTeam.team_name
})
newFixture.save()
results.push(newFixture.toObject())
})
}
}
console.log(results)
res.send(Response("success", "Retrieved fixtures", results))
})
and the result looks something like this:
after await
[]
during await
and therefore an empty array of results is sent before values are added inside. I'm not sure what I'm missing here.

This is a workaround if anyones wondering. Sending the response inside the exec() callback when the forLoop completes bypassing the need for async/await.
if (doesExist) {
let query = FixtureModel.find({ league_name: docs[i].leagueName, $or: [{ homeTeam: { team_name: team} }, { awayTeam: { team_name: team} }] })
await query.exec((err2, docs2) => {
if (err2)
return res.send(Response("failure", "Error occured while retrieving fixtures"))
docs2.forEach((doc2, index) => {results.push(doc2.toObject())})
if(i + 1 == docs.length){
return res.send(Response("success", "Retrieved fixtures", results))
}
})
}

Related

When i push value to empty array it showing Node js

exports.OrderPlace = (req, res) => {
const all_product = req.body.product_info;
let meta = [];
all_product.forEach((element) => {
Ticket.find({ _id: element.product_id }, function (err, docs) {
if (err) return handleError(err);
if (docs[0]._id == element.product_id) {
if (element.quantity < docs[0].quantity) {
meta.push({
cart_id: element.id,
pro_id: element.product_id,
quantity: element.quantity + " Asking quentity is not available!",
});
}
}
});
});
console.log(meta);
};
I'm trying to push cart_id , pro_id, quantity. its loging me empty value please help
Im expecting console.log(meta) values like
[
{
cart_id: "63db8665ba7126c2b35fb231",
pro_id: "63d025a8eefcf49cdcdd5472",
quantity: "36 Asking quentity is not available!",
},
{
cart_id: "63dbc2a7fbf7daf48052189e",
pro_id: "63ce4393c3433881173d1502",
quantity: "40 Asking quentity is not available!",
}
]
wrap the whole code block inside an async function, and use await inside the function to wait for the result of the Ticket.find operation.
exports.OrderPlace = async (req, res) => {
const all_product = req.body.product_info;
let meta = [];
let flag = "";
for (const element of all_product) {
const docs = await Ticket.find({ _id: element.product_id }).exec();
if (docs[0]._id == element.product_id) {
if (element.quantity > docs[0].ticket_quantity) {
flag = "false";
meta.push({
cart_id: element.id,
pro_id: element.product_id,
quantity: element.quantity + " Asking quentity is not available!",
});
}
}
}
console.log({ flag: flag, meta });
};
The easiest way that I found to manipulate the array of objects is using the array methods like filter, map etc...
Maybe following code helps you
exports.OrderPlace = (req, res) => {
const all_product = req.body.product_info;
let meta = [];
all_product.forEach((element) => {
Ticket.find({ _id: element.product_id }, function (err, docs) {
if (err) return handleError(err);
element = element.filter((item) => {
return (docs[0]._id == element.product_id && element.quantity < docs[0].quantity)
})
meta = element.map((item) => {
return {
cart_id: element.id,
pro_id: element.product_id,
quantity: element.quantity + " Asking quentity is not available!",
}
});
});
});
console.log(meta);
};

Wait for mongoose.find inside foreach loop inside callback

How do I wait till data is populated in finalresult before going further?
I tried async and await but did not work as expected and on top of that I am new to nodejs express
exports.getporderlist = (req, res) => {
page = req.params.page || "1";
skip = (page - 1) * 10;
if (req.profile.role.includes(1)) {
Order.find({ status: { $in: [0, 2] } })
.select("_id updatedAt items salesman")
.populate("customer", "customer_name phone")
.sort({ updatedAt: -1 })
.skip(skip)
.limit(10)
.exec((err, orders) => {
if (err) {
return res.status(400).json({
error: "No Orders found",
});
}
let finalresult = [];
orders.forEach((oelement) => {
total = 0;
salesman = oelement.salesman.split(" ")[0];
itemscount = oelement.items.length;
placeorder = true;
oelement.items.forEach((element) => {
total += element.price * element.quantity;
});
//Wait for the bellow data variable to finish populating finalresult
const data = Design.find()
.select("_id sm lm")
.where("_id")
.in(oelement.items)
.exec((err, orders) => {
finalresult.push(orders);
console.log(orders);
});
});
console.log(1);
res.json(finalresult);//getting empty finalresult array on call
});
} else {
res.json({ Message: "Go away" });
}
};
The exec() function do returns promise, so you can user async/await to call it.
Your code will look like this:
exports.getporderlist = async (req, res) => {
try {
if (req.profile.role.includes(1)) {
page = Number(req.params.page) || 1;
skip = (page - 1) * 10;
const orders = await Order.find({
status: {
$in: [0, 2]
}
})
.select("_id updatedAt items salesman")
.populate("customer", "customer_name phone")
.sort({
updatedAt: -1
})
.skip(skip)
.limit(10)
.exec();
let finalresult = [];
for (const oelement of orders) {
let total = 0;
salesman = oelement.salesman.split(" ")[0];
itemscount = oelement.items.length;
placeorder = true;
oelement.items.forEach((element) => {
total += element.price * element.quantity;
});
const data = await Design.find()
.select("_id sm lm")
.where("_id")
.in(oelement.items)
.exec();
finalresult.push(data);
}
res.json(finalresult);
} else {
res.json({
Message: "Go away"
});
}
} catch (err) {
return res.status(400).json({
error: "No Orders found",
});
}
}
you can always call async functions with async/await inside for ..of loops. You can read more here
P.s. Couldn't get a chance to run the code.. Let me know if you have any doubts :)

Await all Mongo queries with Node.js

Code below has a flaw as I am getting array of undefined:
let filters = [];
async function getFilters(tiers) {
return await Promise.all(
tiers.map(async t => {
let id = new ObjectId(t.filter);
filters.push(
await conn.collection('TierScheduleFilter').find({
_id: id
}).toArray(function(err, filter) {
if (err || !filter) {
reject('no filter || error');
}
return filter;
});
);
});
);
}
await getFilters(tiers);
console.log(filters); // 4 filters => [ undefined, undefined, undefined, undefined ]
The code shall retrieve all filters but its all undefined values.
This one seems to be a proper approach:
let filters = [];
async function getFilters(tiers) {
return await Promise.all(
tiers.map(async t => {
let id = new ObjectId(t.filter);
try {
return await conn.collection('TierScheduleFilter').findOne({ _id: id })
} catch (e) {
return e;
}
}))
}

How to implement async in for loop?

I have a collection called 'alldetails' which have the details of some collection
{
"name" : "Test1",
"table_name" : "collection1",
"column_name" : "column1"
},
{
"name" : "Test2",
"table_name" : "collection2",
"column_name" : "column2"
},
{
"name" : "Test3",
"table_name" : "collection3",
"column_name" : "column3"
}
I have collection1,collection2 and collection3 which have column1,column2,colum3 respectively
I have to fetch all the name from the 'alldetails' and I have to get the min and max value of other table based on the column name.
So I want the output like below
{name: ["Test1","Test2","Test3"],
date: [{min_date: "2018-12-01", max_date: "2018-12-31", name: "Test1"},
{min_date: "2018-12-01", max_date: "2018-12-31", name: "Test2"},
{min_date: "2018-12-01", max_date: "2018-12-31", name: "Test3"}]
}
I tried the below code because of non blocking its not waiting the response.
alldetails.find({}, { _id: 0 }).then(async function(result) {
let result_data = {};
let resolvedFinalArray = {};
let array = [];
result_data["name"]= [];
result_data["date"] = [];
resolvedFinalArray = await Promise.all(result.map(async value => {
result_data["name"].push(value.name)
getResult(value.table_name,value.column_name,function(response){
result_data["date"].push({min_date: response.minvalue, max_date: response.maxvalue, name:value.name})
});
}));
setTimeout(function()
{
console.log(resolvedFinalArray);
}, 3000);
});
Please suggest me a solution.
If you want to wait for getResult then you need to return Promise from result.map callback.
You are not pushing anything to resolvedFinalArray so why bother with console.log(resolvedFinalArray)
alldetails.find({}, {_id: 0}).then(async (result) => {
let result_data = {};
result_data["name"] = [];
result_data["date"] = [];
await Promise.all(result.map(value => {
// create Promise that resolves where getResult callback is fired
return new Promise((resolve) => {
getResult(value.table_name, value.column_name, (response) => {
result_data["name"].push(value.name);
result_data["date"].push({
min_date: response.minvalue,
max_date: response.maxvalue,
name: value.name
});
resolve();
});
});
}));
console.log(result_data);
});
or using for loop
alldetails.find({}, {_id: 0}).then(async (result) => {
let result_data = {};
result_data["name"] = [];
result_data["date"] = [];
for (let i = 0; i < result.length; i++) {
const value = result[i];
await new Promise((resolve) => {
getResult(value.table_name, value.column_name, (response) => {
result_data["name"].push(value.name);
result_data["date"].push({
min_date: response.minvalue,
max_date: response.maxvalue,
name: value.name
});
resolve();
});
});
}
console.log(result_data);
});
use async.eachOfLimit if you want to apply an async function on all element of an array:
var async = require("async");
var array = [{_id: "...."},{...},{...}];
async.eachOfLimit(array, 1, function(element, index, cb){
myAsyncFunctionWithMyElement(element, function(err){
return cb(err);
});
}, function(err){
// final callback
});
The array forEach method won't work with async function (unless you do deeply evil things like redefining the prototype). This question has a nice insight of the internal.
If you don't want to rely on external libraries, an easy (and my favourite) approach is something like:
for (let i = 0; i < <your array>.length; i++ ) {
await Promise.all( <your logic> );
}
Just adapt it to your need! :)
You might want to use the for await of loop. See this blog post for details.
This, IMHO, is the most modern way to do it, and it doesn't require you to load any external dependencies, since it is built-in to the language itself. It's basically very similar to the classical for of loop.
This should work, if all lexical scope are taken to consideration. Async each is also is better option it would reduce if else blocks and manage promise for you.
alldetails.find({}, { _id: 0 })
.exec((err, result) => {
if (!err) {
let resolvedFinalArray = [];
result.map((value) => {
resolvedFinalArray.push({
name: value.name,
date: []
});
getResult(value.table_name, value.column_name, (err, response) => {
if (!err) {
resolvedFinalArray[resolvedFinalArray.indexOf(value.name)]['date'].push({
min_date: response.minvalue,
max_date: response.maxvalue,
name:value.name
});
} else {
// Send your error messsage.
// res.status(500).send(err);
}
});
});
console.log(resolvedFinalArray);
// res.send(resolvedFinalArray);
} else {
// Send your error messsage.
// res.status(500).send(err);
}
});

How to get json data from multiple url-pages Node.js?

I am quite new to Node.js and haven't been working with json data before so really hope that you can help me.
I am trying to get all event information from Ticketmaster's API and add specific variables to mongoDB. However, the APIs' that I am currently using are limited to 200 events per page. It is therefore not possible for me to connect the event information with venue information since these are added seperately to mongoDB and are not exhaustive of all event and venue information (not able to connect on ids because of missing event and venue data).
My question is therefore in regards to how I can get all pages into my database at once?
The code that I have written so far looks something like below:
app.get('/tm', (req, res) => {
axios // getting venues
.get('https://app.ticketmaster.com/discovery/v2/venues.json?apikey=myApiKey&page=0&size=200&countryCode=DK')
.then(response => {
const venuesToBeInserted = response.data._embedded.venues.map(venue => { // preparing venues
return {
sourceID: venue.id,
venue: venue.name,
postalCode: venue.postalCode,
city: venue.city.name,
country: venue.country.name,
countryCode: venue.country.countryCode,
address: !!venue.address ? venue.address.line1 : null,
longitude: !!venue.location ? venue.location.longitude : null,
latitude: !!venue.location ? venue.location.latitude : null,
source: 'ticketmaster'
}
})
// Create all venues at once
Venue.create(venuesToBeInserted).then(venues => {
console.log("venues inserted")
axios // getting events and shows - note the page parameter in the api link
.get('https://app.ticketmaster.com/discovery/v2/events.json?apikey=myApiKey&countryCode=DK&size=200&page=0')
.then(response => {
const eventsToBeInserted = response.data._embedded.events.map(events => { // preparing events
const event = events._embedded.attractions[0]
return {
sourceID: event.id,
name: event.name,
slug: slugify(event.name).toLowerCase(),
tags: !!event.classifications ? [event.classifications[0].genre.name, event.classifications[0].subGenre.nam] : [], // duplicate genres occur
// possible tags from ticketmaster: type and subtype
}
})
// Create all events at once
Event.create(eventsToBeInserted).then(events => {
console.log("events inserted")
const showsToBeInserted = response.data._embedded.events.map(show => {
const event = events.find(event => event.sourceID == show._embedded.attractions[0].id);
const venue = venues.find(venue => venue.sourceID == show._embedded.venues[0].id);
if (!!event && !!venue) {
return {
event: event._id,
venue: venue._id,
timezone: show.dates.timezone,
dateStart: !!show.dates.start.dateTime ? show.dates.start.dateTime : show.dates.start.localDate,
tickets: !!show.priceRanges ? {
minPrice: show.priceRanges[0].min,
maxPrice: show.priceRanges[0].max,
currency: show.priceRanges[0].currency
}: {}
}
}
})
// Let's see what we have created in the database
Venue.find({}).select({
name: 1,
slug: -1
}).limit(10).populate('event').populate('venue').then(events => {
console.log(util.inspect(events));
}).catch(err => {
console.error(err);
});
}).catch( err => {
console.error(err)
})
}).catch( err => {
console.error(err)
})
}).catch(err => {
console.error(err)
});
}).catch(err => {
console.error(err)
})
})
EDIT
Using the approach that Jake suggested gave me an error (Error: Requested failed with status code 401). I have tried to search for it online but I cannot figure out why the error happens.. See picture below of part of the error message in my console.log.
error message
You can do this using promises, which you already are using, you just need to chain them together using recursion.
function getVenues(page, size, venues) {
page = page || 0;
size = size || 200;
venues = venues || [];
return axios
.get(`https://app.ticketmaster.com/discovery/v2/venues.json?apikey=myApiKey&page=${page}&size=${size}&countryCode=DK`)
.then(response => response.data._embedded.venues)
.then(rawVenues => {
rawVenues.forEach(venue => venues.push(venue));
if (rawVenues.length < size) {
// All done return the compiled list.
return venues;
}
// Recurse over the next set of venues by adding another promise to the chain.
return getVenues(page + 1, size, venues);
});
}
function getEvents(page, size, events) {
page = page || 0;
size = size || 200;
events = events || [];
return axios
.get(`https://app.ticketmaster.com/discovery/v2/events.json?apikey=myApiKey&countryCode=DK&size=${size}&page=${page}`)
.then(response => response.data._embedded.events)
.then(rawEvents => {
rawEvents.forEach(event => events.push(event));
if (rawEvents.length < size) {
// All done return the compiled list.
return events;
}
// Recurse over the next set of events by adding another promise to the chain.
return getEvents(page + 1, size, events);
});
}
app.get('/tm', (req, res) => {
getVenues().then(rawVenues => {
const venuesToBeInserted = rawVenues.map(venue => {
return {
sourceID: venue.id,
venue: venue.name,
postalCode: venue.postalCode,
city: venue.city.name,
country: venue.country.name,
countryCode: venue.country.countryCode,
address: !!venue.address ? venue.address.line1 : null,
longitude: !!venue.location ? venue.location.longitude : null,
latitude: !!venue.location ? venue.location.latitude : null,
source: 'ticketmaster'
};
});
// Return promise so errors bubble up the chain...
return Venue.create(venuesToBeInserted).then(venues => {
console.log("venues inserted");
// Return promise so errors bubble up the chain...
return getEvents().then(rawEvents => {
const eventsToBeInserted = rawEvents.map(rawEvent => {
const event = events._embedded.attractions[0];
return {
sourceID: event.id,
name: event.name,
slug: slugify(event.name).toLowerCase(),
tags: !!event.classifications ? [event.classifications[0].genre.name, event.classifications[0].subGenre.nam] : []
};
});
// Return promise so errors bubble up the chain...
return Event.create(eventsToBeInserted).then(events => {
console.log("events inserted");
const showsToBeInserted = rawEvents.map(show => {
const event = events.find(event => event.sourceID == show._embedded.attractions[0].id);
const venue = venues.find(venue => venue.sourceID == show._embedded.venues[0].id);
if (!!event && !!venue) {
return {
event: event._id,
venue: venue._id,
timezone: show.dates.timezone,
dateStart: !!show.dates.start.dateTime ? show.dates.start.dateTime : show.dates.start.localDate,
tickets: !!show.priceRanges ? {
minPrice: show.priceRanges[0].min,
maxPrice: show.priceRanges[0].max,
currency: show.priceRanges[0].currency
} : {}
}
}
});
// Do something with the found shows...
});
});
});
}).then(() => { // This then is fired after all of the promises above have resolved...
return Venue.find({}).select({
name: 1,
slug: -1
}).limit(10).populate('event').populate('venue').then(events => {
console.log(util.inspect(events));
res.send(events);
});
}).catch(err => { // Catches any error during execution.
console.error(err);
res.status(500).send(err);
});;
});

Resources