MongoDB query with promise not executing if statement (MongoDB node.js driver) - javascript

I'm trying to get my head around why the if(!document) is not executed when a document is not found, but instead the app jumps straight to the catch block and returns the 500 status instead?
To clarify, there's no problem with the id sent via req.body.id, if I send a valid ID that matches a doc in the db the success block is executed. The problem is when I send an ID that doesn't match a doc the if (!document) {} block is not executed, but instead the catch block is executed.
My intention is this (if the code example is not clear enough):
if the document is not found return:
return res.status(401).json({ message: 'Unauthorized.' }
If the document is found:
return res.status(200).json({ message: 'Authorized.' });
If there's a db / server error:
return res.status(500).json({ message: 'Internal server error.' })
Small code example below:
const login = (req, res) => {
userSchema
.findById(req.body.id)
.then((document) => {
if (!document) {
return res.status(401).json({ message: 'Unauthorized.' }).end();
}
return res.status(200).json({ message: 'Authorized.' });
})
.catch((err => return res.status(500).json({ message: 'Internal server error.' });
};

In order to successfully execute this part:
userSchema
.findById(req.body.id)
...
req.body.id has to be a value castable to an ObjectId. Otherwise you will see this error CastError: Cast to ObjectId failed for value..., which, of course, will trigger the catch block.
So, for example, if req.body.id is 604156ee6db28527531bd612, which is perfectly castable to ObjectId, your query will actually execute and, if no results are found, will trigger your if block.
If, however, req.body.id is, say, sh21343dfs or an empty string, then findById will throw the CastError mentioned above and send you straing into the catch block.
So just use the catch as your cue to send back something like a User Not Found error, because there are very few other things can trigger that catch block in findById
Or you could expicitly check for the validity of the string like so:
const ObjectID = require('mongodb').ObjectID;
...
...
if(ObjectID.isValid(req.body.id))
return res.status(401).json({ message: 'Unauthorized.' }).end();

Related

In nodejs Rest api call, puppeteer page evaluate not returning the response and throws the error

In nodejs Rest api call, puppeteer page evaluate not returning the response and throws the error.
How to return object after executing all the steps, without async
app.get("/api/register", function (req, res) {
res = page.evaluate((res) => {
webex.meetings
.register()
.then(() => {
console.log("Authentication#register() :: successfully registered");
return res.status(200).json({ message: "Successfully Registered" });
})
.catch((error) => {
console.error( "Authentication#register() :: error registering", error);
return res.status(400).json({ message: "Successfully Registered" });
})
}, res);
});
error
:\Users\sansubbu\git\webRTC\node_modules\puppeteer-core\lib\cjs\puppeteer\common\Connection.js:115
const stringifiedMessage = JSON.stringify(Object.assign({}, message, { id }));
^
TypeError: Converting circular structure to JSON
--> starting at object with constructor 'Socket'
| property 'parser' -> object with constructor 'HTTPParser'
--- property 'socket' closes the circle Recursive objects are not allowed.
at JSON.stringify ()
at Connection._rawSend (C:\Users\sansubbu\git\webRTC\node_modules\puppeteer-core\lib\cjs\puppeteer\common\Connection.js:115:41)
at CDPSessionImpl.send (C:\Users\sansubbu\git\webRTC\node_modules\puppeteer-core\lib\cjs\puppeteer\common\Connection.js:320:82)
at ExecutionContext._ExecutionContext_evaluate (C:\Users\sansubbu\git\webRTC\node_modules\puppeteer-core\lib\cjs\puppeteer\common\ExecutionContext.js:211:46)
res is a complex, circular structure that only works in the Node environment. Even if you could, passing it to the browser console via page.evaluate() would take it out of Node, where it belongs, leaving it in an environment where it doesn't make any sense (browsers can't respond to requests as if they were a server).
Instead, try returning a boolean and branching on that on the Node side, where req/res are in their natural environment:
app.get("/api/register", async (req, res) => {
const success = await page.evaluate(async () => {
try {
await webex.meetings.register();
return true;
}
catch (err) {
return false;
}
});
if (success) {
console.log("Authentication#register() :: successfully registered");
return res.status(200).json({message: "Successfully Registered"});
}
console.error("Authentication#register() :: error registering", error);
// probably not the message you want but left as-is...
return res.status(400).json({message: "Successfully Registered"});
});
This is untested since you haven't provided a complete, reproducible example.
page.exposeFunction is another possible tool for triggering Node code based on a condition in the browser, but that seems like overkill here.
Finally, I'm not sure what page is, but typically you need a different page for each request. See this answer for suggested Express + Puppeteer boilerplate.

where to should write res.send() in node.js app

In my app I have a category collection that saves the category title with its image ID. The image is saved in another collection with its meta data like path , type and so on. So for retrieving category I should retrieve category image in image collection by its ID and add image path to category object that is retrieved from category collection and send it to client...But I don't know where should I send categories to client?
When I send the response face this error :
throw er; // Unhandled 'error' event
^
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at ServerResponse.setHeader (_http_outgoing.js:561:11)
at ServerResponse.header (H:\node.js\online-store\app\node_modules\express\lib\response.js:771:10)
at ServerResponse.send (H:\node.js\online-store\app\node_modules\express\lib\response.js:170:12)
at ServerResponse.json (H:\node.js\online-store\app\node_modules\express\lib\response.js:267:15)
at ServerResponse.send (H:\node.js\online-store\app\node_modules\express\lib\response.js:158:21)
at H:\node.js\online-store\app\controllers\pcategory.controller.js:123:19
at H:\node.js\online-store\app\node_modules\mongoose\lib\model.js:4845:18
at processTicksAndRejections (internal/process/task_queues.js:77:11)
Emitted 'error' event on Function instance at:
at H:\node.js\online-store\app\node_modules\mongoose\lib\model.js:4847:15
at processTicksAndRejections (internal/process/task_queues.js:77:11) {
code: 'ERR_HTTP_HEADERS_SENT'
}
This is my code :
exports.getAll = async (req, res) => {
try{
const categories = await ProductCategory.find({});
categories.map(async(category)=>{
await File.findById(category.imageID).exec(function(err,file){
if(err){
console.log(err)
}else if(file) {
category.imagePath = file.file_path;
tempCategories.push(category)
}
res.send(tempCategories);
})
})
return res.send(tempCategories);
}catch {
res.json(err =>{
console.log(err);
res.status(500).send({
message:
err.message || "There is an error in retrieving category"
});
})
}
}
The problem is that nothing in your code is waiting for the asynchronous operations you're doing in your map callback to complete, so it does the res.send at the end right away — and then does res.send again within the map callback later when the async operations complete. Instead, wait for them to finish and send the result.
Also, you're using res.send where I suspect you want res.json, and using res.json later incorrectly (it doesn't take a callback).
See comments:
exports.getAll = async (req, res) => {
try {
// Get the categories
const categories = await ProductCategory.find({});
// Get the files for the categories, wait for the result
const result = await Promise.all(categories.map(async (category) => {
const file = await File.findById(category.imageID).exec();
// You probably can't modify the `category` object, so let's create
// and return a new object
return {...category, imagePath: file.file_path};
}));
// Send the result converted to JSON
return res.json(tempCategories);
} catch (err) { // Accept the error
// Send an error response
res.status(500).json({
message: err.message || "There is an error in retrieving category"
});
}
};
Side note: Your original code was using map without using the array it creates. That's an antipattern (sadly it seems to be one someone somewhere is teaching). I wrote up why and what to do instead here. (In my update to your code, I still use map, but I use the array it creates, passing it to Promise.all so we can wait for all those promises to settle.)
Your Code Like this,
Now Issue is You are sending two times Headers.
You can use like this, Firstly Declare array and push into it what you need and then last of your logic return it or send it.
exports.getAll = async (req, res) => {
try {
const categories = await ProductCategory.find({});
let tempCategories = []; // New Line
await Promise.all(categories.map(async (category) => {
await File.findById(category.imageID).exec(function (err, file) {
if (err) {
console.log(err)
} else if (file) {
category.imagePath = file.file_path;
tempCategories.push(category)
}
});
return category;
}));
res.send(tempCategories);
} catch {
res.json(err => {
console.log(err);
res.status(500).send({
message:
err.message || "There is an error in retrieving category"
});
})
}
}

deleteMany showing 0 deleted but actually deleting the documents

this controller is deleting all the todos of particular user but in response it is showing {n: 0, deletedCount: 0, ok: 1}, does anyone knows the solution of it?
exports.deleteAll = async (req, res) => {
const { id } = req.user
console.log('id', id);
try {
// const deleteAllTodos = await TodoModel.findByIdAndDelete(id)
const deleteAllTodos = await TodoModel.deleteMany({ createdBy: id}, function (err) {
console.log('err', err) })
res.send({ success: true, message: "All todos deleted successfully", deleteAllTodos })
} catch (error) {
console.log('Error:', error.message);
res.status(500).json({
message: "Internal server error",
success: false,
error: error.message
});
}
}
From mongoose Query guides
Don't mix using callbacks and promises with queries, or you may end up
with duplicate operations. That's because passing a callback to a
query function immediately executes the query, and calling then()
executes the query again.
So what you did there was calling deleteMany twice, first using the callback and then using the async/await. The second attempt will try to delete nothing because the first attempt has already deleted them.
You could try using either one:
...
const deleteAllTodos = await TodoModel.deleteMany({ createdBy: id }).exec();
res.send({
success: true,
message: "All todos deleted successfully",
deleteAllTodos
})
Don't worry about error handling using callback as you have already done it with the try/catch block which will catch if any error happened on deleteMany.

Try/Catch catches promise reject... any way around that?

I have to use try/catch because MongoDb's ObjectId() will break/error if arg is to short.
The promise .catch never fires when the try is good...it seems that the outer catch(e) will catch the promise reject(). Is this expected behavior? anyway around that?
function deleteUser(req, res) {
try {
var userId = { '_id': ObjectId(String(req.params.user_id)) };
var petData = _getAllData(userId);
petData
.then(function(doc) {
data.deleteAllData(doc);
result = user.remove(userId);
_returnApi(result, res);
})
.catch(function(err) {
console.log(`error delete user -${err}`);
res.status(500).json({ error: "error deleting user" });
});
}
catch(e) {
res.status(400).json({ error: "user id format incorrect" });
}
}
Per those two issues 1 and 2 are discussed in Mongoose issue site, after mongoose v4.2.5, the Invalid ObjectId could be caught in find method through exec(), here are the sample codes test under mongoose v4.4.2
Foo.find({_id: 'foo'}) // invalid objectId
.exec()
.then(function(doc) {
console.log(doc);
})
.catch(function(err) {
console.log(err);
})
Output:
{ [CastError: Cast to ObjectId failed for value "foo" at path "_id"]
message: 'Cast to ObjectId failed for value "foo" at path "_id"',
name: 'CastError',
kind: 'ObjectId',
value: 'foo',
path: '_id',
reason: undefined }
However, according to this issue, the update method is still failed.
The way I see it, you can reduce it to single catch block, but return different messages based on the error type then:
function deleteUser(req, res) {
let userId;
return Promise.resolve({ '_id': ObjectId(String(req.params.user_id))})
.then(_userId => {
userId = _userId;
return _getAllData(userId);
}).then(doc => {
data.deleteAllData(doc);
result = user.remove(userId);
return _returnApi(result, res);
}).catch(err => {
if(!userId) return res.status(400).json({ error: "user id format incorrect" });
console.log(`error delete user -${err}`);
res.status(500).json({ error: "error deleting user" });
});
}
}

Meteor.js: Error not caught on client in method call

I am trying to throw a user defined error while updating a documents in mongoDb, if any error occures in Method. I am calling method and trying to catch the error, but i am not getting one. Error is printed only in server console. how can i catch the error in client side?
My code sample seems like this:
//Method
methodName: (userData) ->
if(Meteor.isServer and this.userId)
User.update {_id:this.userId},{$set:{username:userData.username, "profile.name": userData.name ,"emails.$.address": userData.email}}, (error) ->
if error and error.code == 11000
throw new Meteor.Error 403, 'Email already in used'
//client Side
$meteor.call('methodName',result).then ((success) ->
console.log success // is undefined in both case, when updates and in case of error
if success
console.log 'data updated'
), (err) ->
console.log err // not entered in this region
Your code has a large amount of errors.
Meteor.methods({
methodName: function(userData){
// you have to create the $set hash programatically first
var setHash = {
"$set": {
"username": userData.username,
"profile.name": userData.name,
// what's going on here with the dollar sign you originally had?
"emails.0.address": userData.email
}
};
if( Meteor.isServer && this.userId() ){
// It's Users, not User
return Users.update( { _id: this.userId() }, setHash, function(error, numberOfAffectedDocuments){
if(error && error.code == "11000"){
// Error names have to be a string
throw new Meteor.error("403", "Email already in use.");
} else {
return "Success! The number of affected documents is " + numberOfAffectedDocuments;
});
};
}
});
// in your original code you never sent your Meteor Method any arguments when calling it
Meteor.call('methodName', userDataObject, function(error, result){
if(error){
console.log("there was an error: ", error);
} else {
console.log("method call was a success: ", result);
};
});
References:
http://docs.mongodb.org/manual/reference/operator/update/set/#set-elements-in-arrays
http://docs.meteor.com/#/full/meteor_user
Your code has a couple of misconceptions
1) A method is a synchronous call. This means if it returns or runs completely before an error is fired the error callback will not be called on the client.
This means you need to use synchronous code throughout. At the moment, you are using a callback.
You can use this approach instead:
Meteor.methods methodName: (userData) ->
if Meteor.isServer and #userId
return User.update({ _id: #userId }, $set:
username: userData.username
'profile.name': userData.name
'emails.$.address': userData.email)
This will throw an error that will be received on the client with reason "Internal Server Error". As a 'catch-all' error. The difference between your code is there is no callback.
You can catch your specific duplicate key error using the try..catch pattern using this syntax.
2) User.update {_id:this.userId} will always run. You are looking for a "duplicate key", 11000, error when you update the document. This isn't the best approach to doing this. You should directly check for the email beforehand.
3) A method should return a value. At the moment, you don't return anything. You can only use one for the result, a callback or checking the value given back by the method. At the moment, you do both, so the result of User.update is undefined. This is why you see undefined. This should work:
Meteor.methods methodName: (userData) ->
if Meteor.isServer and #userId
if(User.emails.address':userData.email})) throw new Meteor.Error(500, "Email already exists");
return User.update({ _id: #userId }, $set:
username: userData.username
'profile.name': userData.name
'emails.$.address': userData.email)
else return false
So here you would directly check for a user having used the email and throw the Error & update it if it's not used. There are no callbacks so it should return a value to Meteor.call on the client.

Categories