Multiple queries in NodeJS and MongoDB - javascript

I've got a NodeJS/Express/MongoDB app. For one of my endpoints I'm trying to get some stats from the database and I'm having trouble doing it using promises.
I know that db doesn't get moved between the different .thens but no matter how I re-arrange the code I can't get anything out of the console.log() except the first users count. I've also tried saving db into a variable declared at the start of the function but that doesn't help either.
What's the proper way to make multiple queries to MongoDB in a promisified way?
Node Code:
function getStats(num){
var stats = "";
MongoClient.connect(`${mongoString}/SiteUsers`)
.then(db => {
db.db().collection("Users").find({}).count()
.then( userCount => {
stats += `Users: ${userCount}\n`
return db;
})
.then( adminCount => {
db.db().collection("Users").find({admin:true}).count()
.then( adminCount => {
stats += `Admins: ${adminCount}\n`
})
})
.then( data => {
console.log(`Stats are now: \n${stats}`);
})
})
.catch(err => {
if(err) console.log(err);
});
}
Thanks!

There are several ways that you can handle the order of your promises. One of which is to use async/await functionality.
async function getStats(num){
try {
const conn = await MongoClient.connect(`${mongoString}/SiteUsers`)
const userCount = await conn.db().collection("Users").find({}).count()
const adminCount = await conn.db().collection("Users").find({admin:true}).count()
console.log(`User count: ${userCount}\nAdmin count: ${adminCount}`)
} catch (err) {
console.log(err)
}
}

Related

How to wait for Promise.all to resolve before returning values in async function (express JS)

I'm pretty new to JS and especially async programming. I am trying to build an Express+React app that displays a gitHub user's info (including a few repos with 5 commits).
To do the latter, I am using Promise.all and map to get the commits for each repo in an array, and returning an array with the commits added to each repo.
When I console.log the new repo in the map function, it works, but when I try to return the array, the objects are undefined.
I assume this is because the return is executing before the Promises have resolved. Can anyone help me out with this?
//takes a username and collection of repos and finds their commits using fetch to github API. Returns the array of repos with their commits as a new property
const getGitHubCommits = async (username, repos) => {
try {
const repositories = await Promise.all(
repos.map(async (repo) => {
fetch(`https://api.github.com/repos/${username}/${repo.name}/commits?per_page=5`).then(response => response.json())
.then(async response => {
repo.commits = response.slice(0, 5)
console.log(repo)
return await repo
})
})
);
console.log(repositories)
return repositories
} catch (error) {
console.log(error)
return "No commits found found"
}
}
You dont need async / await here at all. As soon as you put async infront of an function, the function will return an promise and you need to resolve that promise then.
Here is a better version:
const getGitHubCommits = (username, repos) => {
return Promise.all(
repos.map((repo) =>
fetch(
`https://api.github.com/repos/${username}/${repo.name}/commits?per_page=5`
)
.then((response) => response.json())
.then((response) => {
repo.commits = response.slice(0, 5)
return repo
})
)
)
}
This is much cleaner. Later you need to resolve the promise:
getGitHubCommits('...', ['...'])
.then((result) => {
console.log(result)
})
.catch((err) => {
console.log(err)
})
Or you can use here async / await if you call it inside an async function:
try {
let commits = await getGitHubCommits('..', ['..'])
console.log(commits)
} catch (err) {
console.log(err)
}

What is the best way to perform multiple operations in a Knex transaction

I'm relatively new to knex and transactions. Basically I have to perform a bunch of batch inserts and updates across different POSTGRES tables and wanted to understand the best possible way to do it.
Is the following a correct approach for this? I'm especially struggling with the batch-update part.
let fieldsToInsert = [{name:'Mike', age: 57, weight:66, gender:'M'}, ...]
let _user = {user_id:99, city:6}
return db.transaction(trx => {
return trx.insert(fieldsToInsert)
.into("users")
.then(() => {
const queries = fieldsToInsert.map((user) => {
db("biodata").update({"name": user.name}).where({"id": user.age})
})
return Promise.all(queries)
}).then(() => {
const queries = fieldsToInsert.map((user) => {
db("weight_and_gender".update({"weight": user.weight, "gender": user.gender}))
})
return Promise.all(queries)
})
}).catch((err) => {
console.log(err)
})
await db.transaction(async trx => {
const insertedRows = await trx('users').insert(fieldsToInsert).returning('*');
for (let row in insertedRow) {
await trx('biodata').update({name : row.name}).where('id', row.id);
await trx('weight_and_gender').update({weight : row.weight, gender: row.gender}).where('id', row.id);
}
});
Trying to run updates in parallel with Promise.all does not help anything. DB driver will buffer those anyways and run them sequentially in either way if they are executed in the same transaction.

How can I push data to an array inside an async request to the database?

I am trying to make a request to the database (mongoDB) and save its return in a list of objects but the list is not getting filled. Here is the code
router.get('/all/:studentEmail', auth, async (req, res) => {
try {
const student = await Student.findOne({ email: req.params.studentEmail });
if (!student) {
return res.status(404).json({ msg: 'Student not found' });
}
var list = [];
student.exercises.map(async (exercise) => {
list.unshift(await Exercise.findById(exercise));
});
res.json(list);
} catch (err) {
console.error(err.message);
res.status(500).send('Server error');
}
});
The database query await Exercise.findById(exercise) returns correctly the object, but res.json(list); returns empty. Do anyone know how to solve it?
The base issue is that res.json() executes way before student.exercises.map(async (exercise) => { completes. Putting await into map doesn't wait for each and every item in the async loop to process. Either use something like Promise.all() or use a for loop (other strategies can be used also). Decide which to use based on whether you can process in parallel or need to process in series. Try the following using Promise.all to execute async requests parallel using then on each Promise to execute an operation against list:
router.get("/all/:studentEmail", auth, async (req, res) => {
try {
const student = await Student.findOne({ email: req.params.studentEmail });
if (!student) {
return res.status(404).json({ msg: "Student not found" });
}
var list = [];
await Promise.all(
student.exercises.map((exercise) =>
Exercise.findById(exercise).then((result) => list.unshift(result))
)
);
res.json(list);
} catch (err) {
console.error(err.message);
res.status(500).send("Server error");
}
});
Also, an alternative to unshift and just return the results if they are not nested, if they are nested you can consider flat():
const list = await Promise.all(
student.exercises.map((exercise) => Exercise.findById(exercise))
);
return res.json(list);
Hopefully that helps!

Javascript promise chain hell

I'm having a problem with promise chains that I don't know how to resolve. Summary of my code: I do a mongoose query for a specific user, fetch his CarIds and then query each car for his details and return these details via JSON response.
let carsDetails = [];
User.findById(userId)
.then(user => {
const carIds = user.carsDetails;
carIds.forEach((carId) => {
Car.findById(carId)
.then(car => {
console.log(car);
carsDetails.push(car);
})
.catch(err => { throw err; });
});
return res.status(200).json({ data: carsDetails });
})
.catch(error => {
throw error;
});
The problem is that no cars get actually pushed onto carsDetails array on the carsDetails.push(car); line, because it jumps to return statement before it manages to fill up the array. Is there a workaround that could do those queries and return a result in a form of an array, object...? I tried writing everything in async await form too with self-made async forEach statement, but it doesn't help me. Any suggestions? I already tried with Promise.all(), but didn't manage to fix the issue. Thanks!
You'll need to collect the promises of your Car.findById(carId) calls and use Promise.all() to wait for all of them before responding. You can use array.map() to map each ID to a promise from Car.findById().
User.findById(userId)
.then(user => {
const carIds = user.carsDetails;
const carPromises = carIds.map(carId => Car.findById(carId))
return Promise.all(carPromises)
})
.then(cars => {
res.status(200).json({ data: cars })
})
.catch(error => {
throw error
})
If you can have a lot of cars to find, you may want to do your query in a single request, no need to stack multiple promises :
User.findById(userId)
.then(user => {
const carIds = user.carsDetails;
// if carsDetails is not an array of objectIds do this instead :
// const carIds = user.carsDetails.map(id => mongoose.Types.ObjectId(id));
return Car.find({ _id: { $in: carIds });
})
.then(userCars => {
res.status(200).json({ data: userCars })
})
await/async is the way to go, with await/async you use regular for ... of loops instead of forEach.
async function getCarDetails() {
let carsDetails = [];
let user = await User.findById(userId);
const carIds = user.carsDetails;
for (let carID of carIds) {
let car = await Car.findById(carId)
console.log(car);
carsDetails.push(car);
}
return res.status(200).json({
data: carsDetails
});
}
Or you use Promise.all and map instead of for ... of
async function getCarDetails() {
let user = await User.findById(userId);
const carIds = user.carsDetails;
let carsDetails = await Promise.all(carIds.map(carID => Car.findById(carID)));
return res.status(200).json({
data: carsDetails
});
}
Those two solutions are slightly different. The second version with the map will send all requests to the DB at once, and then waits until they are all resolved. The first one will send the request one after another. Which one is better depends on the use-case, the second one could lead to request peeks, and might be easier be abused for DDoS.
Try to use async/await to solve this problem. It will be more readable and clean
async function getCarDetail() {
let carsDetails = []
try {
const user = await User.findById(userId)
const carIds = user.carsDetails
for (let i = 0; i < carIds.length; i++) {
const carId = carIds[i]
const car = await Car.findById(carId)
carsDetails.push(car)
}
return res.status(200).json({ data: carsDetails })
} catch(error) {
console.log(error)
}
}

getting value from a chained promises

im really new to this of promises and im getting headaches trying to understand this, so now im trying to get an answer from a method that returns a promise, then i catch the value in a conditional and make some other operations
let addService = async(req, res) => {
checkCategoryExists(param).then(result => {
if(result){
// code here
}
}).catch(err => console.log(err));
}
let checkCategoryExists = async(param) => {
let docs = db.collection(collectionName).doc(param);
docs.get()
.then(categoryDoc => {
if(categoryDoc.exists){
if(categoryDoc.data().param== param){
return true;
}
} else {
return false;
}
})
.catch(err => false);
}
the method "checkCategoryExists" is a query to a firestore db. When i tried to check if result variable is true or false, it happens to be undefined. its not with ".then()" that i get to catch the value from the returned promise? if someone can help me, thanks in advance
So as mentioned above I think your issue is based on not returning the results of your document search so both examples below handle that.
I also noticed that you were using async tags on your functions but not ever using await so I wanted to give examples of both ways of doing it in case you wanted to use Async/Await but weren't certain how.
In the promise chain example I'm relying on the syntax of arrow functions to create the returns (no {} means return right side of equation) since none of your given code requires data manipulation before return (if your actual code needs that you should of course use brackets and remember your return statement :D )
If you choose to use Async/Await you can structure the code much more closely to synchronous examples and use try catch statements. Sometimes I find this syntax more clear if I have to do a lot of processing/manipulation before returning a result.
Good Luck :)!
// Promise Chains
const addService = (req, res) =>
checkCategoryExists(param)
.then(result => /* do stuff */)
.catch(err => console.error(err.message));
const checkCategoryExists = param =>
db.collection(collectionName.doc(param)
.get()
.then(categoryDoc =>
Promise.resolve((categoryDoc.exists && categoryDoc.data().param === param))
);
// OR using Async/Await
const addService async (req, res) => {
try {
const catExists = await checkCategoryExists(param);
if (!catExists) {
throw new Error('error code');
}
// Do Stuff.
} catch (error) {
console.error(error);
}
};
const checkCategoryExists = async param => {
try {
const docs = await db.collection(collectionName)
.doc(param)
.get();
return (docs.exists && docs.data().param === param);
} catch (error) {
console.error(error)
}
}

Categories

Resources