mongoose findOne : different between async & sync - node.js

I have confuse with know what is different between :
var users = Users.findOne({key})
var users = await Users.findOne({key})

var users = Users.findOne({key})
Users.findOne() method returns a promise which can either be resolved or rejected. See promises
So in above case users variable actually contains a promise object and if you want to get the resolved data you have to do something like
users.then(data=>{
// your db query data
}).catch(err=>{
// error if something goes wrong
})
The below syntax with await actually gives you the resolved data of promise see await
var users = await Users.findOne({key})
so you will have data like this [{key:value}] of your mongodb stored in users variable.
Also await is valid only in async function so you need to wrap it inside async function
async function foo(){
var users = await Users.findOne({key})
}

Related

mongoose Query was already executed

I was trying to learn a MERN stack beginner tutorials and i encountered this problem the query was already executed I'm getting this error whenever .findOne() is executed:
MongooseError: Query was already executed: elders.findOne({ _id: new ObjectId("636caae0e3c24f1df7b4e6b7...
router.put("/updateElders", async (req,res)=>{
const newAge = req.body.newAge
const id = req.body.id
try{
await EldersModel.findById(id, (error, eldertoUpdate) =>{
eldertoUpdate.age = Number (newAge);
eldertoUpdate.save();
})
}catch(err){console.log(err);}
You should decide on whether to use callbacks or promises. You chose to use await (used for awaiting promises) and callbacks (your second parameter passed to findById. You should decide on which one to use, e.g.:
router.put("/updateElders", async (req,res)=>{
const newAge = req.body.newAge
const id = req.body.id
try{
const eldertoUpdate = await EldersModel.findById(id);
eldertoUpdate.age = Number(newAge);
await eldertoUpdate.save();
}catch(err){
console.log(err);
}
this code uses promises returned from findById and save calls and awaits those.

How can I async await multiple asynchronous MongoDB findOne functions with NodeJS

I am using "await" on multiple MongoDB ".findOne" functions to different collections one at a time. I would like to let them all run asynchronously together and somehow know when they are all ready to use.
Instead of doing this:
async function myFunction() {
const collection_1 = await firstCollection.findOne({})
const collection_2 = await secondCollection.findOne({})
const collection_3 = await thirdCollection.findOne({})
console.log(collection_1, collection_2, collection_3)
}
Can I do something like this?
async function myFunction() {
[collection_1, collection_2, collection_3]
await new Promise(() => {
collection_1 = firstCollection.findOne({})
collection_2 = secondCollection.findOne({})
collection_3 = thirdCollection.findOne({})
})
console.log(collection_1, collection_2, collection_3)
}
I don't know how to correctly use Promises to do this.
For tracking the parallel execution of multiple promise-based asynchronous operations, use Promise.all():
async function myFunction() {
const [collection_1, collection_2, collection_3] = await Promise.all([
firstCollection.findOne({}),
secondCollection.findOne({}),
thirdCollection.findOne({})
]);
console.log(collection_1, collection_2, collection_3)
}
Promise.all() will reject if any of the promises you pass it reject. If you want all results, regardless of whether one might reject, you can use Promise.allSettled() instead. See the doc for exactly how its resolved value works.

Loop through an array to generate a new array. It returns 0 at the end of the loop

There are 4900 books in the database. I expect to have the same number of books in the new array at the end of the loop. However, I am getting a zero length for the booksArray. What could be the problem and possible solution?
const getOverview = async(req, res) => {
const books = await Book.find();
const booksArray = new Array();
books.forEach(book => {
const url =`https://www.goodreads.com/book/isbn/${book.isbn}key=${process.env.KEY}`;
request.get(url).then(result => {
parseString(result, (error, goodReadsResult) => {
const goodreadsBook = goodReadsResult.GoodreadsResponse.book[0];
booksArray.push(goodreadsBook);
})
});
})
console.log(booksArray.length);
};
First a bit of an overview. Node.js does all networking as non-blocking and asynchronous. That means when you make a networking request, it starts the operation and immediately returns and continues executing the rest of your code. A callback or promise associated with that asynchronous operation gets called sometime later.
In your specific case, your entire .forEach() loop runs to completion starting all the networking requests in it before any of them have finished. Thus you are doing console.log(booksArray.length); before any of your callbacks have run and thus the array is still empty.
Any time you want to sequence or coordinate more than one asynchronous operation in node.js you will want to use promises. It's been built into the language for a couple years now and it's just a massively better way to program with asynchronous operations. I would suggest you find some good tutorials on how promises work and learn them.
Then, the library you are using request() is old and has been put in maintenance mode and does not support promises. There are a variety of more modern alternatives. I personally use the got() library and that's what I've illustrated below.
Then, I don't know what your parseString() function is, but it apparently doesn't use promises and appears to be asynchronous so, in order to use it in a promise-based workflow, I "promisified" it using util.promisify() which is built-into node.js. If it has a promise interface, you should just use that directly.
So anyway, here's what I would suggest:
const got = require('got');
const {promisify} = require('util');
const ps = promisify(parseString);
// this function returns a promise
async function getOverview(req, res) {
const books = await Book.find();
const booksArray = await Promise.all(books.map(book => {
const url =`https://www.goodreads.com/book/isbn/${book.isbn}key=${process.env.KEY}`;
let bookData = await got(url);
return ps(bookData);
}));
console.log(booksArray.length);
// make the booksArray be the resolved value of the promise returned
// by this async function
return booksArray;
}
This makes all your networking calls in parallel and then uses Promise.all() to indicate when they are all done and to collect all the results in order.
If, for some reason, you can't make all these requests in parallel (like there are so many that the target server objects), then you can run them one at a time too like this:
const got = require('got');
const {promisify} = require('util');
const ps = promisify(parseString);
// this function returns a promise
async function getOverview(req, res) {
const books = await Book.find();
const booksArray = [];
for (let book of books) {
const url =`https://www.goodreads.com/book/isbn/${book.isbn}key=${process.env.KEY}`;
let bookData = await got(url);
let data = await ps(bookData);
booksArray.push(data);
}
console.log(booksArray.length);
// make the booksArray be the resolved value of the promise returned
// by this async function
return booksArray;
}
And, since these are async functions, they return a promise so that caller must use await or .then() to get the value from the promise.
getOverview(...).then(results => {
console.log(results);
}).catch(err => {
console.log(err);
});
If you actually intend to pass req and res into that function and use them there, then you will probably need local error handling too so if any of your await operations reject, you can catch that error and send an error response. You would do that with try/catch inside the function body. You can probably just use one top level try/catch and then send an error response inside the catch handler.

Promise returns undefined and data after

For some reason I am getting undefined before the actual data using the following code
async findOne(query: string, parameters: string): Promise<T> {
const stmt: sqlite3.Statement = await this.db.prepare(query)
const info: T = stmt.get(parameters)
this.db.close()
return info
}
const user = await respository.findOne('SELECT * FROM users WHERE id = ?', targetUser.id)
console.log(user)
The console log outputs undefined and a object after that, what is the reason for this?
Probably you will also need await here:
const info: T = await stmt.get(parameters);
From the documentation here it seems that .get is a classic callback function, so you will probably need to wrap it inside a Promise before using it with await.
Probably the same is true about this.db.prepare(query)? Checkout util.promisify from the standard node library if you don't want to do the promise wrapping yourself.
Also, you can't call an async function in open code. Try this:
(async () => {
const user = await respository.findOne('SELECT * FROM users WHERE id = ?', targetUser.id)
})();
Hope this helps!

How to update a list of users in Firestore using Firebase admin sdk?

So I'm learning how to use the firebase admin sdk and I've come across a problem that I'm not understanding enough to solve. Basically, I've queried my database and gotten a subset of users that I would like to update with the same value. So I stored the id's of each user that I want to update in an array, now I am trying to loop through that array and update each user. But I'm having a problem implementing this part and it has to do with the fact that .update() returns a promise and I'm not 100% how to handle this in a loop...
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp()
exports.getAllUsers = functions.https.onRequest((request, response) => {
const db = admin.firestore()
const users = db.collection('users').where('role', '==', 'artist').get()
.then(function (querySnapshot) {
//const users = querySnapshot.data()
const promises = []
querySnapshot.forEach(function (doc){
promises.push(doc)
})
return Promise.all(promises) //returning promises sends the resolved results to
}) //to the next .then()
.then(function (person){
let results = []
person.forEach(function(personSnap){
//const data = personSnap.data()
results.push(personSnap.id)
})
return results // results is the array of ids to update
})
//This .then() is where I have trouble understanding how to update
.then(function (ids){
for(var i = 0; i<ids.length; i++){
var artistsRef = db.collection('users').doc(ids[i]);
artistsRef.update({ //Should not be a return statement. This fixed my problem. Otherwise it would update the first user in the array and then leave the for loop.
'free_credits': '2'
})
}
return 'finished'
})
.then(function(reply) {
return response.send(reply)
})
.catch(function (error){
response.send(error)
})
})
I feel like I should be returning 'artistsRef.update()' since it is a promise but I think that leads to unexpected results.
If I don't return the update() then ESLint throws the error: "Each then() should return a value or throw"
You're returning early out of the final then() callback, before a response is sent. Maybe you should add another then() just for sending the result after whatever final updates are complete.

Resources