MongoDB How to catch errors in deleteMany - node.js

I have looked at MongoDB documentation for deleteMany() but it seems the only error it throws is WriteConcernError.
I am using Insomnia to make my requests.
Here is my request:
DELETE HTTP://localhost:5000/api/users/delete/usernames?usernames=["a","b","c"]
As you can see I have an array in query string
so I pass that to my function
# user.controller.js
function _deleteByUsernames(req, res, next) {
userService.deleteByUsernames(JSON.parse(req.query.usernames))
.then(() => res.status(200).json({"message": "User(s) successfully deleted!"}))
.catch(err => next(err));
}
# user.service.js
async function _deleteByUsernames(usernames) {
try {
console.log(usernames);
await User.deleteMany({username: {$in: usernames}});
} catch (err) {
throw err;
}
}
I know there no documents with usernames a, b and c
but deleteMany() doesn't return any error something like "Coulnd't find given parameter" etc.
because I don't want to response with "User(s) successfully deleted".
How can I catch that error if there is one.
Or How should I handle that?

You may change your functions to below,
# user.controller.js:
put async/await in function, and add code in try/catch block, and pass res as param in service function deleteByUsernames,
async function _deleteByUsernames(req, res, next) {
try {
await userService.deleteByUsernames(res, JSON.parse(req.query.usernames));
} catch (err) {
next(err);
}
}
# user.service.js:
deleteMany(), This function calls the MongoDB driver's Collection#deleteMany() function. The returned promise resolves to an object that contains 3 properties:
ok: 1 if no errors occurred
deletedCount: the number of documents deleted
n: the number of documents deleted. Equal to deletedCount.
async function _deleteByUsernames(res, usernames) {
let response = await User.deleteMany({ username: { $in: usernames } });
// ERROR
if (response.ok != 1) {
res.status(400).json({ "message": "User(s) not deleted, Something want wrong!" });
}
// SUCCESS
else {
res.status(200).json({
"message": `${response.deletedCount} User(s) successfully deleted out of ${response.n}"
});
}
}
Code is not tested, you can workaround and see what happens!

I think there is no error for no found parameters.
I don't know this is better than nothing for now.
I am not going to mark this as answered because I don't think this is the answer
async function _deleteByUsernames(usernames) {
return await User.deleteMany({username: {$in: usernames}})
.then(result => {
console.log(result);
return (result.deletedCount === 0 ?
"None of the selected user(s) deleted!":
(result.deletedCount !== usernames.length ?
`${result.deletedCount} out of ${usernames.length} selected user(s) successfully deleted!`:
"All selected user(s) successfully deleted!"))
})
.catch(err => {
return `Delete failed with error: ${err}`;
})
}

You can save your delete result in a variable and check for the error
async function _deleteByUsernames(usernames) {
try {
console.log(usernames);
let userDeleteResult = await User.deleteMany({username: {$in: usernames}});
if(!userDeleteResult ){
res.json({status: false, error: 'Some error'}) // or pass the error object here
}
} catch (err) {
throw err;
}
}

Related

Struggling with calling a function that uses promises in node.js

I am struggling with some code... The 2 examples below I would think would work the same but the second example throws an error? I am also struggling to figure out the error, it's not bubbling up? Admittedly I am not a seasoned node developer so any guidance would be much appreciated! If it's relevant the create method in the module is calling the sequelize create.
This works
var p1 = deliverabiltyConfigs.create2(cfgObject);
return Promise.all([p1]).then(function([res1]) {
res.json({result: res1})
});
This does not
deliverabiltyConfigs.create2(cfgObject).then(res1 =>{
res.json({result: res1})
})
Here is the function that I am calling in a controller module
exports.create2 = (dConfig) => {
DeliverabilityConfig.create(dConfig)
.then(data => {
return data
})
.catch(err => {
return {
message:
err.message || "Some error occurred while createing this config."
};
});
};
The create2 function always returns null, so neither invocation will work. Promise.all([p1]) hides the problem, returning a promise to perform an array of no promises.
create2(cfgObject).then(res1 =>{ attempts to invoke then() on null, generating a more obvious error. But neither way works.
Fix by deciding which promise syntax you want, using each as follows:
Using original promise syntax....
exports.create2 = dConfig => {
// note the return
return DeliverabilityConfig.create(dConfig)
.catch(err => {
const message = err.message || "Some error occurred while createing this config.";
return { message };
});
};
// caller
deliverabiltyConfigs.create2(cfgObject).then(result =>{
res.json(result);
})
With recent syntactic sugar...
exports.create2 = async (dConfig) => {
try {
// its fine to not await here, since the caller will await
// but just to illustrate how you might perform more async work here...
return await DeliverabilityConfig.create(dConfig);
} catch (err) {
const message = err.message || "Some error occurred while createing this config."
return { message }
}
}
// caller
var result = await deliverabiltyConfigs.create2(cfgObject);
res.json(result);
Use Promise.all() to run >1 promise concurrently. You've only got one promise in the OP, so no reason for it here.

What is the proper way of handling errors that are thrown from withing a callback function?

I am developing a NodeJS application and I am using mongoose for saving data into my MongoDB database.
My controller can take a POST request at the /register url with some data. That looks like this:
router.post("/register", async (req: Request, res: Response) => {
const accountModel: IRegistrationAccount = {
firstName: req.body.firstName,
lastName: req.body.lastName,
email: req.body.email,
password: req.body.password,
repeatedPassword: req.body.repeatedPassword,
};
try {
registerAccount(accountModel);
res.status(OK).send("Registration successful.");
} catch (err) {
res.status(NOT_ACCEPTABLE).send(err);
}
});
As you can see, I want to return an error message to the user so that they know exactly what went wrong. This is the registerAccount method:
export function registerAccount(accountModel: IRegistrationAccount) {
if (accountModel.firstName.length === 0)
throw "Your first name may not be empty.";
if (accountModel.email.length < 3) throw "Your email is too short.";
if (accountModel.password !== accountModel.repeatedPassword)
throw "The passwords You entered don't match.";
if (accountModel.password.length < 8) throw "Your password is too short.";
const account = new Account(accountModel);
account.save(function (err) {
if (err) return logger.err(err);
return logger.info("Created account.");
});
}
When there is something wrong the the data that the user entered, I return an error message using throw, which is then later caught in the controller. The problem is: how do I know if the callback function inside save threw an error and how do I handle that error? This is my first time working with Node, I tried searching around but can't find a suitable answer.
save method can return Promise so you don't need to use a callback at all:
export async function registerAccount(accountModel: IRegistrationAccount) {
if (accountModel.firstName.length === 0)
throw "Your first name may not be empty.";
if (accountModel.email.length < 3) throw "Your email is too short.";
if (accountModel.password !== accountModel.repeatedPassword)
throw "The passwords You entered don't match.";
if (accountModel.password.length < 8) throw "Your password is too short.";
const account = new Account(accountModel);
await account.save();
}
and add await at the line where you call this function:
try {
await registerAccount(accountModel);
logger.info("Created account.")
res.status(OK).send("Registration successful.");
} catch (err) {
logger.err(err)
res.status(NOT_ACCEPTABLE).send(err);
}
I'd promisify account with util.promisify, and then return the Promise and .catch it in the caller:
return accountSavePromisified().then(() => {
logger.info("Created account.");
});
try {
registerAccount(accountModel)
.then(() => {
res.status(OK).send("Registration successful.");
})
.catch((err) => {
// Catch asynchronous errors (thrown by `.save`):
res.status(NOT_ACCEPTABLE).send(err);
})
} catch (err) {
// Catch synchronous errors (thrown by your validator):
res.status(NOT_ACCEPTABLE).send(err);
}
If you don't care about differentiating errors thrown by .save from errors thrown by your validator, you could also await the call of registerAccount instead of calling .then on it.
You could also consider making the control flow a bit easier to understand by returning error strings from registerAccount instead of throwing, eg return 'Your first name may not be empty.':
const result = registerAccount(accountModel);
if (typeof result === 'string') {
res.status(NOT_ACCEPTABLE).send(result);
return;
}
result.then(() => {
res.status(OK).send("Registration successful.");
})
.catch((err) => {
res.status(NOT_ACCEPTABLE).send(result);
});

Mongoose document created. But change is not reflected immediately in the collection

favoriteRouter
.route('/:dishId')
.post(cors.corsWithOptions, authenticate.verifyUser, (req, res, next) => {
Favorites.findOne({ user: req.user._id })
.then(
(favoriteList) => {
//***SECTION A START//***
if (favoriteList == null) {
Favorites.create({
user: req.user._id,
})
.then(
(favoriteList) => {
console.log('promise resolved');
},
(err) => {
console.log('promise error');
next(err);
}
)
.catch((err) => next(err));
}
//***SECTION A END//***
Favorites.findOne({ user: req.user._id })
.then((favoriteList) => {
//***SECTION B START//***
if (favoriteList != null) {
favoriteList.dishes.push(req.params.dishId);
favoriteList
.save()
.then(
(favoriteList_c) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.json(favoriteList_c);
},
(err) => {
next(err);
}
)
.catch((err) => {
next(err);
});
} else {
err = new Error(
'Something wrong with favorite list document of user ' +
req.user._id
);
err.status = 404;
return next(err);
}
//***SECTION B END//***
})
.catch((err) => next(err));
},
(err) => next(err)
)
.catch((err) => next(err));
});
If a user post on /:dishId and the favourite document is not there for that user then a new document is created
in SECTION A (marked in code). The document is created fine as it prints promise resolved. But in SECTION B the else part is executed that means the newly created document is not found. But if the user tries again means in next go it can find that document and it gets updated in SECTION B IF block. Is there something I am missing. I am a beginner in nodejs, Please help!
You have to first understand the way NodeJs asynchronous code work. The default behavior of NodeJs is that whenever it finds an I/O operation, it delegates that opeation to either OS or worker thread based on OS capability in handling it and moves to the next line of code.
According to your code, the first Favorite.findOne() is called and while it is being executed by the engine, control jumps over to the next line of code, which is the second Favorite.findOne() and it tries to find the document. But At this point the document has not been created yet, So that's the reason when you run for the first time, it doesn't find the record, but for the second time onwards the document has been created using the Favorite.create() inside the first findOne's then().
So you need to re-factor your code by putting the second findOne() inside the firstOne. Well, you know what, you don't need to write the Favorite.findOne() two times. One findOne() is sufficient to accomplish your requirement.
Mongoose leverages promises, it means we can use async/await in the controller method.
favoriteRouter
.route('/:dishId')
.post(cors.corsWithOptions, authenticate.verifyUser, async (req, res) => {
try {
// find the FavoriteList
let favoriteList = await Favorites.find({
user: req.user._id
});
// using a library called lodash for simplicity
// you have to install it using npm i lodash and
// require at the top using let _ = require('lodash')
//If not found, create one and assign it to favorite list
if (_.isEmpty(favoriteList)) {
favoriteList = await Favorites.create({
user: req.user._id
});
}
// at this point, you must have a favoriteList either a found one or brand new
favoriteList.dishes.push(req.params.dishId)
let favoriteList_c = await favoriteList.save();
return res.json(favoriteList_c)
} catch (err) {
//handle error
res.status(501).send({message: 'unable to perform operation'});
}
});
I have added async to the controller callback function here and reomved the next parameter, as we don't need it. For reference visit this.
NOTE: the code I've wrtitten may not work if you simply copy/paste it in your program but the approach is fairly straight forward and you may need to do some tweakings based on Mongoose API documentation especailly for save() and create().
I am sure they will return the object after creating it.
Good Luck!!
There is no problem in your code but when the findOne of section A is getting executed at the same time section B code gets executed.
Using promise.then creates a complex code. Use async/await instead.
The following is just a small snippet from your code to use async/await
let favoriteList = await Favorites.findOne({ user: req.user._id });
if (favoriteList == null) {
await Favorites.create({
user: req.user._id,
})
}
favoriteList = await Favorites.findOne({ user: req.user._id })
if (favoriteList != null) {
favoriteList.dishes.push(req.params.dishId);
await favoriteList.save();
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.json(favoriteList_c);
} else {
err = new Error(
'Something wrong with favorite list document of user ' +
req.user._id
);
}

Getting Error: Can't set headers after they are sent

I am trying to implement search functionality in Node, mongoose.
There is two parameter I like to search upon, either by name or artist. If any of the two matches with the current database it should return value(making it restful)
However, it is sending response Error: Can't set headers after they are sent.
and Unhandled promise rejections are deprecated and even the response which i am getting is empty
I am trying to execute two queries in it, which i think might be the problem. How should i write it, or what is a correct way to write these type of functionality
Here is my current code
app.get('/search/:textValue', controller.findData)
and the findData
exports.findData = (req, res)=>{
const searchParam = req.params.textValue;
let storeResult = []
if(searchParam==null|| searchParam == undefined || searchParam==""){
return res.status(500).json("Send a valid input")
}
else{
Song.find({artists: new RegExp(searchParam, "i")}).lean().then((data)=>{
storeResult[0].push(data)
}).catch((err)=>{
return res.send(err)
})
Song.find({name: new RegExp(searchParam, "i")}).lean().then((data)=>{
storeResult[1].push(data)
}).catch((err)=>{
return res.send(err)
})
return res.send(storeResult)
}
}
They are working for single queries perfectly fine, what changes should be made over here ?
The way you have it you're using res.send(storeResult) before you fill in storeResult. How so? You fill it in with your .then() callbacks, which haven't yet been invoked.
Try chaining your then callbacks.
Song.find({artists: new RegExp(searchParam, "i")}).lean()
.then((data)=>{
storeResult.push(data);
})
.then(() => {
Song.find({name: new RegExp(searchParam, "i")}).lean()
.then((data)=>{
storeResult.push(data)
})
.then(() => {
console.log(storeResult)
res.send(storeResult)
})
})
.catch((err)=>{
console.log("Here is error")
console.log(err)
res.send(err)
})
}
Hint. Step-into in your debugger is useful for troubleshooting this kind of code.
Try this:
exports.findData = (req, res)=>{
let count=0;
const searchParam = req.params.textValue;
let storeResult = []
if(searchParam==null|| searchParam == undefined || searchParam==""){
return res.status(500).json("Send a valid input")
}
else{
Song.find({artists: new RegExp(searchParam, "i")}).lean().then((data)=>{
storeResult[0].push(data)
}).catch((err)=>{
count++;
return res.send(err)
})
if(count == 0) {
Song.find({name: new RegExp(searchParam, "i")}).lean().then((data)=>{
storeResult[1].push(data)
}).catch((err)=>{
count++;
return res.send(err)
})
}
if(count == 0) {
return res.send(storeResult)
}
}
}
Problem
You're starting with empty array let storeResult = []
Then you access its first element (which does not exist) storeResult[0].push(data)
This will trigger your catch callback. And then do a res.send(err)
Even if you called return it will still continue in (req, res) => {} . This is because the return is only for the (err) => { // } callback
Same thing with storeResult[1].push(data)
Finally you call return res.send(storeResult) which effectively finishes your (req, res) => {} callback and return another response to the client
Solution:
When you push to your storeResult array, omit the index. Like this
storeResult.push(data)
Note
Even when pushing correctly, an error might happen while accessing the database. This is why you also need to chain your callbacks like O. Jones answer says

Correct way of handling promisses and server response

I am trying to improve my code in node.js / sail.js and I am fighting server response in promisses.
When you look at the first .then function you can see that method returns false in case of forbidden access or notFound. Then, in the next .then functions I must check if the return type is === false to skip to section and avoid sending http headers twice. Can this be improved somehow, to skip all next .then methods in case of failure? I can throw an Exception to go in the last .catch but then there must be a case to switch between all possible states. (i.e. forbidden, serverError or even not found)
Notification.findOne({id: req.param('id')})
.then(function(notification) {
if (!notification) {
res.notFound();
return false;
}
if (notification.triggeredBy != req.session.user.id) {
res.forbidden();
return false;
}
return notification;
})
.then(function(notification) {
if (notification === false) {
return false;
}
return Notification.update(notification.id, actionUtil.parseValues(req));
})
.then(function(notification) {
if (notification === false) {
return false;
}
res.json(notification);
})
.catch(function(err) {
sails.log(err);
res.serverError({message: 'A server error occurred.'});
})
If I would do this, first I seperate logic and receving/sending function. Second I specify listing of error codes. And it will be like that:
NotificationService.js
/*
Listing of error codes: {
* [1] Object not found
* [2] Forbidden
* [3] Server error
}
*/
module.exports = {
nameOfMethod: function(ID, sessionID) {
return new Promise(function(resolve, reject) {
Notification.findOne({ id: ID })
.then(function(notification) {
if (!notification) return reject({ error_code: 1 });
if (notification.triggeredBy !== sessionID) return reject({ error_code: 2 });
Notification.update(notification.id, actionUtil.parseValues(req))
.then(function(notification) {
return resolve(notification); // finally return our notification
})
.catch(function(err) {
sails.log.error(err); // It's good when log is classified. In this case is error
return reject({ message: 'A server error occurred.' });
});
})
.catch(function(err) {
sails.log.error(err);
return reject({ message: 'A server error occurred.' });
});
});
}
};
NotificationController.js
module.exports = {
notifyMe: function(req, res) {
const ID = req.param('id'), sessionID = req.session.user.id;
NotificationService.nameOfMethod(ID, sessionID)
.then(function(notification) {
return res.send(notification);
})
.catch(function(err) {
switch (err.error_code) {
case 1:
return res.notFound(err);
case 2:
return res.forbidden(err);
default:
return res.serverError(err);
}
});
}
};
In case where I use switch I think it is better way to select right response but on this time I haven't any idea
See how filtered .catch() is implemented in Bluebird - it can be useful in your case to throw all errors you need but avoid having a big switch/case block in the catch handler:
.catch(
class ErrorClass|function(any error)|Object predicate...,
function(any error) handler
) -> Promise
.caught(
class ErrorClass|function(any error)|Object predicate...,
function(any error) handler
) -> Promise
This is an extension to .catch to work more like catch-clauses in
languages like Java or C#. Instead of manually checking instanceof or
.name === "SomeError", you may specify a number of error constructors
which are eligible for this catch handler. The catch handler that is
first met that has eligible constructors specified, is the one that
will be called.
Example:
somePromise.then(function() {
return a.b.c.d();
}).catch(TypeError, function(e) {
//If it is a TypeError, will end up here because
//it is a type error to reference property of undefined
}).catch(ReferenceError, function(e) {
//Will end up here if a was never declared at all
}).catch(function(e) {
//Generic catch-the rest, error wasn't TypeError nor
//ReferenceError
});
See: http://bluebirdjs.com/docs/api/catch.html#filtered-catch
Instead of:
return false;
you can use:
return Promise.reject(someReason);
or:
throw someReason;
and you won't have to check for those false values - just use (possibly multiple) catch handlers.

Resources