Why items are not being pushed in array - mongodb

I am using MongoseDB in order to receive some information about an item. When i try to search for it, it finds it with no trouble, but for some reasons this function is not pushing them into my array. I think this might be because of some async functions and that the console.log() is triggered before any item is being pushed in there.
const getOrders = function(allOrders){
let promise = new Promise((succ, fail)=>{
let ordersTodisplay = []
for (let order of allOrders) {
if (!(order.orderId === null || order.orderItem === null)){
postMong.findById(order.orderItem, function (err, item) {
ordersTodisplay.push(item)
})
}
}
if(ordersTodisplay.length > 0){
succ(ordersTodisplay)
} else{
fail("no items")
}
})
return promise
}
router.get('/accountpage',function(req,res){
const userDB = req.session.username
if (userDB !== undefined && userDB){
userForm.findOne({ username : userDB }, function (err, user) {
const userOrders = user.userOrders;
if (userOrders.length > 1) {
getOrders(userOrders).then((result)=>{console.log(result)}, (fail)=>{console.log(fail)})
res.render('../view/accountpage',{username: userDB,orders: itemsToDisplay});
}
else{
res.render('../view/accountpage',{username: userDB,orders: "There are no orders"});
}
});
} else {
res.redirect("/login")
}
});
The result is : no items

You have to for the database call to complete and then push the data in the array like this, using async-await:
const getOrders = function(allOrders){
let promise = new Promise(async (succ, fail)=>{
let ordersTodisplay = []
for (let order of allOrders) {
if (!(order.orderId === null || order.orderItem === null)){
await postMong.findById(order.orderItem, function (err, item) {
ordersTodisplay.push(item)
})
}
}
if(ordersTodisplay.length > 0){
succ(ordersTodisplay)
} else{
fail("no items")
}
})
return promise
}

Your code is quite nested and that makes it hard to reason about what is happening.
To break down your code, you:
get a single user that has several order IDs referenced
load each order
respond with those orders (although you return itemsToDisplay that doesn't seem to be defined anywhere, so I'm a bit confused)
I'd try to capture that logical pattern in the code. A good trick is returning early to make the code less nested and interdependent:
router.get('/accountpage', function(req,res){
const userDB = req.session.username;
if (!userDB){
res.redirect("/login");
return;
}
loadUserOrders(userDB)
.then(function(orders) {
if(orders.length > 0) {
res.render('../view/accountpage', {username: userDB,orders: orders});
return;
}
// Note: consider returning just the empty array here, that already implies no orders
res.render('../view/accountpage', {username: userDB, orders: "There are no orders"});
})
.catch(function(error) {
//TODO: render error -- case not covered by your code
});
});
// Because this is an async function you can now await inside it, meaning no need for the callback syntax for mongoose
async function loadUserOrders(username) {
const user = await userForm.findOne({ username: username });
// Promise.all runs in parallel several promises and returns an array containing their results
// .map here turns the list of userOrders into a list of promises getting each order
return await Promise.all(user.userOrders.map((userOrder) => postMong.findById(userOrder.orderItem));
}
Notice how this code highlights that you are not explicitly handling the error case from loading orders.
You can further simplify this by using something like express-async-handler which will let your endpoint function be async as well:
const asyncHandler = require('express-async-handler');
router.get('/accountpage', asyncHandler(async function(req,res){
const userDB = req.session.username;
if (!userDB){
res.redirect("/login");
return;
}
// Note: there is no try-catch around loadUserOrders right now, so errors will result in a 500 courtesy of express-async-handler -- better than before
const orders = await loadUserOrders(userDB);
if(orders.length > 0) {
res.render('../view/accountpage', {username: userDB,orders: orders});
return;
}
// Note: consider returning just the empty array here, that already implies no orders
res.render('../view/accountpage', {username: userDB, orders: "There are no orders"});
}));
I think using async/await syntax all the way through leaves the code more consistent and easier to reason about than the mix of callbacks, new Promise and await that was suggested in another answer. In the end, the endpoint is not very complex, and I think the code can reflect that.

Related

Why is Axios data undefined when stored in a variable?

I have the following code I am working on. This is implemented on a Vue app and uses a number of methods which each return with an Axios promise. I am trying to chain these so when a new review is submitted, the server checks if a movie exists already. If not, it creates a new movie. Then it should create a new review using postReview with a parameter of movieId. A movieId is required to create a new review. The methods checkMovieExists() and postMovie() both return a movieId as a response.
The problem is that when I log x.data() or y.data() into the console, the movieId is displayed correctly. However, if I assign x.data or y.data to movieId, it is undefined. This means I can't use it as a parameter to post a movie.
submit() {
let movieId = 0;
this.checkMovieExists(this.movie.imdb_id)
.then((x) => {
console.log(x.data);
if (x.data == 404) {
this.postMovie(this.movie.imdb_id, this.movie.original_title).then(
(y) => {
console.log(y.data); //Displays correctly
movieId = y.data;
console.log(movieId); //Displays as undefined
}
);
} else {
movieId = x.data;
}
})
.then(this.postReview(movieId));
},
(Btw, I am aware of the bug where a movie id is 404. This is my next task!)
I would advise you to stay away from callback hell and to use async/await.
async submit() {
let movieId = 0;
const checkMovieResponse = await this.checkMovieExists(this.movie.imdb_id);
if (checkMovieResponse.data == 404) {
const postMovieResponse = await this.postMovie(this.movie.imdb_id, this.movie.original_title);
movieId = postMovieResponse.data;
}
else {
movieId = checkMovieResponse.data;
}
await this.postReview(movieId);
}
Your problem should be fixed with my solution (there was an asynchronous issue with your code).
Your postMovie callback was executed after your checkMovie callback (causing an "undefined" (should be 0 by just reading your code) movieId in your postReview method).
If for some reasons, you cannot use async/await, here is your "fixed" code:
submit() {
this.checkMovieExists(this.movie.imdb_id)
.then((x) => {
if (x.data == 404) {
this.postMovie(this.movie.imdb_id, this.movie.original_title).then(
(y) => {
this.postReview(y.data)
}
);
} else {
this.postReview(x.data);
}
});
}
By the way, if the data from your Axios response is a number (for your identifier), I would advise you to use the === operator instead of the ==.
Good luck with your project!

In Mongoose Model.find() and Model.find().exec() produce the same result. So why bother using Model.find().exec()?

I noticed that Model.find() seems to produce the same outcome as Model.find().exec(). Both seem to retrieve the appropriate record/document as illustrated in the following two snippets of code:
//without exec
export const getUser = async (req, res, next) => {
try{
let mongoDocument = await User.find({name: "OrphanAnnie"})
res.status(200).json(mongoDocument);
} catch (err) {
console.log(err)
}
}
//with exec
export const getUser = async (req, res, next) => {
try{
let mongoDocument = await User.find({name: "OrphanAnnie"}).exec()
res.status(200).json(mongoDocument);
} catch (err) {
console.log(err)
}
}
Which is the recommended approach? In Mongoose - What does the exec function do? the post suggests that with exec() a Mongoose query can be built without using callback syntax. But since the query seems to work without using exec() at all why bother using it?
Mongoose have explained the difference of both in promises,
There are two alternatives for using await with queries:
await Band.find();
await Band.find().exec();
As far as functionality is concerned, these two are equivalent. However, we recommend using .exec() because that gives you better stack traces.
With the example:
const doc = await Band.find({ name: "Guns N' Roses" }); // works
const badId = 'this is not a valid id';
try {
await Band.find({ _id: badId });
} catch (err) {
// Without `exec()`, the stack trace does **not** include the
// calling code. Below is the stack trace:
//
// CastError: Cast to ObjectId failed for value "this is not a valid id" at path "_id" for model "band-promises"
// at new CastError (/app/node_modules/mongoose/lib/error/cast.js:29:11)
// at model.Query.exec (/app/node_modules/mongoose/lib/query.js:4331:21)
// at model.Query.Query.then (/app/node_modules/mongoose/lib/query.js:4423:15)
// at process._tickCallback (internal/process/next_tick.js:68:7)
err.stack;
}
try {
await Band.find({ _id: badId }).exec();
} catch (err) {
// With `exec()`, the stack trace includes where in your code you
// called `exec()`. Below is the stack trace:
//
// CastError: Cast to ObjectId failed for value "this is not a valid id" at path "_id" for model "band-promises"
// at new CastError (/app/node_modules/mongoose/lib/error/cast.js:29:11)
// at model.Query.exec (/app/node_modules/mongoose/lib/query.js:4331:21)
// at Context.<anonymous> (/app/test/index.test.js:138:42)
// at process._tickCallback (internal/process/next_tick.js:68:7)
err.stack;
}
Other as well: Queries are not promises:
Mongoose queries are not promises. They have a .then() function for co and async/await as a convenience. If you need a fully-fledged promise, use the .exec() function.
const query = Band.find({name: "Guns N' Roses"});
assert.ok(!(query instanceof Promise));
// A query is not a fully-fledged promise, but it does have a `.then()`.
query.then(function (docs) {
// use docs
});
// `.exec()` gives you a fully-fledged promise
const promise = query.exec();
assert.ok(promise instanceof Promise);
promise.then(function (docs) {
// use docs
});

How can i render the page after one query ( find or save ) is performed on Mongodb

I am new to MongoDB . I have written code in which I am searching the data from the database ,
it works fine for the first search and gives the result but after the result is achieved then if I again perform some query then it do not works that is it keeps on rendering the page in which query is being made .
Below is the code snippet .
app.get('/problems/:a?',(req,res)=>{
var getpath = req.params.a;
problemModel.find({path:getpath},function(err,data){
if(err) throw err;
res.render(''+getpath,{record:data[0]});
conn.close();
});
});
Please help me out .
Here's your solution. Using async/await operators, instead of callbacks, the code is cleaner.
app.get('/problems/:path', async (req, res) => {
const { path } = req.params; // Equal to "const path = req.params.path"
try {
const data = await problemModel.find({ path: path });
res.render(path, { record: data[0] });
} catch (e) {
res.render('path-to-error-page');
}
});

mongodb force synchronous to find documents [duplicate]

I'm using the Node.JS driver for MongoDB, and I'd like to perform a synchronous query, like such:
function getAThing()
{
var db = new mongo.Db("mydatabase", server, {});
db.open(function(err, db)
{
db.authenticate("myuser", "mypassword", function(err, success)
{
if (success)
{
db.collection("Things", function(err, collection)
{
collection.findOne({ name : "bob"}, function(err, thing)
{
return thing;
});
});
}
});
});
}
The problem is, db.open is an asychronous call (it doesn't block), so the getAThing returns "undefined" and I want it to return the results of the query. I'm sure I could some sort of blocking mechanism, but I'd like to know the right way to do something like this.
ES 6 (Node 8+)
You can utilize async/await
await operator pauses the execution of asynchronous function until the Promise is resolved and returns the value.
This way your code will work in synchronous way:
const query = MySchema.findOne({ name: /tester/gi });
const userData = await query.exec();
console.log(userData)
Older Solution - June 2013 ;)
Now the Mongo Sync is available, this is the right way to make a synchronous MongoDB query in Node.js.
I am using this for the same. You can just write sync method like below:
var Server = require("mongo-sync").Server;
var server = new Server('127.0.0.1');
var result = server.db("testdb").getCollection("testCollection").find().toArray();
console.log(result);
Note: Its dependent on the node-fiber and some issues are there with it on windows 8.
Happy coding :)
There's no way to make this synchronous w/o some sort of terrible hack. The right way is to have getAThing accept a callback function as a parameter and then call that function once thing is available.
function getAThing(callback)
{
var db = new mongo.Db("mydatabase", server, {});
db.open(function(err, db)
{
db.authenticate("myuser", "mypassword", function(err, success)
{
if (success)
{
db.collection("Things", function(err, collection)
{
collection.findOne({ name : "bob"}, function(err, thing)
{
db.close();
callback(err, thing);
});
});
}
});
});
}
Node 7.6+ Update
async/await now provides a way of coding in a synchronous style when using asynchronous APIs that return promises (like the native MongoDB driver does).
Using this approach, the above method can be written as:
async function getAThing() {
let db = await mongodb.MongoClient.connect('mongodb://server/mydatabase');
if (await db.authenticate("myuser", "mypassword")) {
let thing = await db.collection("Things").findOne({ name: "bob" });
await db.close();
return thing;
}
}
Which you can then call from another async function as let thing = await getAThing();.
However, it's worth noting that MongoClient provides a connection pool, so you shouldn't be opening and closing it within this method. Instead, call MongoClient.connect during your app startup and then simplify your method to:
async function getAThing() {
return db.collection("Things").findOne({ name: "bob" });
}
Note that we don't call await within the method, instead directly returning the promise that's returned by findOne.
While it's not strictly synchronous, a pattern I've repeatedly adopted and found very useful is to use co and promisify yield on asynchronous functions. For mongo, you could rewrite the above:
var query = co( function* () {
var db = new mongo.Db("mydatabase", server, {});
db = promisify.object( db );
db = yield db.open();
yield db.authenticate("myuser", "mypassword");
var collection = yield db.collection("Things");
return yield collection.findOne( { name : "bob"} );
});
query.then( result => {
} ).catch( err => {
} );
This means:
You can write "synchronous"-like code with any asynchronous library
Errors are thrown from the callbacks, meaning you don't need the success check
You can pass the result as a promise to any other piece of code

How to get Meteor.Call to return value for template?

I've tried to understand this post regarding this concept, however, I'm failing to get it. I have the following simple setup:
/server/test.js
Meteor.methods({
abc: function() {
var result = {};
result.foo = "Hello ";
result.bar = "World!";
return result;
}
});
/client/myapp.js
var q = Meteor.call('abc');
console.log(q);
This structure returns to the console undefined.
If I change the myapp.js file to:
Meteor.call('abc', function(err, data) {
!err ? console.log(data) : console.log(err);
}
I receive the Object in my console.
Ideally this is what I'd like to be able to do, but it doesn't work, stating in the console: Cannot read property 'greeting' of undefined
/client/myapp.js
var q = Meteor.call('abc');
Template.hello.greeting = function() {
return q.foo;
}
Any help in passing the data from the server object into the template would be greatly appreciated. I'm still learning JavaScript & Meteor.
Thanks!
From the Meteor.call documentation:
On the client, if you do not pass a callback and you are not inside a stub, call will return undefined, and you will have no way to get the return value of the method. That is because the client doesn't have fibers, so there is not actually any way it can block on the remote execution of a method.
So, you'll want to do it like this:
Meteor.call('abc', function(err, data) {
if (err)
console.log(err);
Session.set('q', data);
});
Template.hello.greeting = function() {
return Session.get('q').foo;
};
This will reactively update the template once the data is available.
This happens because Npm.require has Async behavior. That's the reason that you have to write a callback for Meteor.call.
But there is a solution, just use install(mrt add npm) and you'll get a function named Meteor.sync(//...) with this you can do both games: sync and async in your Meteor.call().
Reference: http://www.sitepoint.com/create-a-meteor-app-using-npm-module/
You can get the return value of a Meteor method for use in a template by using a reactive variable. Check out the working demonstration on Meteorpad
I went for a ghetto solution. But, it works for me, which is what matters, to me. Below is my code, which, in concept, I think, solves OP's problem.
In the client's main.js:
Meteor.setInterval(function() {
confirmLogin();
}, 5000);
This runs the confirmLogin() function every five seconds.
The confirmLogin function (in the client's main.js):
function confirmLogin() {
Meteor.call('loggedIn', function (error, result) {
Session.set("loggedIn", result);
});
}
The loggedIn method (in the server's main.js):
loggedIn: function () {
var toReturn = false;
var userDetails = Meteor.user();
if (typeof userDetails["services"] !== "undefined") {
if (typeof userDetails["services"]["facebook"] != "undefined") {
toReturn = true;
}
}
return toReturn;
},
The relevant helper:
loggedIn: function () {
return Session.get("loggedIn");
}