Custom Error in Sails ORM (Waterline) callback - node.js

In my user model I have something like this:
register: function(data, next) {
User.findOne({email:data.email}).exec(function findOneUserCB(err, user) {
if (!err && user) {
return next(new Error('Email already exist.'));
}
// other things
});
}
I'm basically trying to return a custom error when the user is found but there isn't any other error from waterline.
But this doesn't work, sails complains that TypeError: Cannot call method 'toString' of undefined.
So I've tried to emulate a waterline error:
//...
var error = {
code: 'E_UNIQUE',
details: 'Invalid',
model: 'user',
invalidAttributes: {
hase: []
},
status: 400
}
return next(error);
//...
This works but it feels very hackish. Isn't it a better way to pass a custom error from within a query callback? I couldn't find any documentation about this topic

You can try something like this
register: function(data, next) {
User.findOne({email:data.email}).exec(function findOneUserCB(err, user) {
if(user){
var alreadyExists = new Error();
alreadyExists.message = require('util').format('User already exists');
alreadyExists.status = 400;
cb(alreadyExists);
}
// other things
});

Related

Mongoose - Return error in 'pre' middleware

How can I send a custom error message if my validation fails in schema.pre('save')? For example, if I have a chat feature, where you create a new conversation, I want to check if a conversation with the given participants already exists, so I can do:
ConversationSchema.pre('save', function(next, done) {
var that = this;
this.constructor.findOne({participants: this.participants}).then(function(conversation) {
if (conversation) {
// Send error back with the conversation object
} else {
next();
}
});
});
Pass an Error object when calling next to report the error:
ConversationSchema.pre('save', function(next, done) {
var that = this;
this.constructor.findOne({participants: this.participants}).then(function(conversation) {
if (conversation) {
var err = new Error('Conversation exists');
// Add conversation as a custom property
err.conversation = conversation;
next(err);
} else {
next();
}
});
});
Docs here.
I agree with JohnnyHK's answer except that it doesn't seem possible to add custom properties to the Error object. When receiving the error and trying to access that property the value is undefined so the solution is that you can send a custom error message but not add custom properties. My code would be something like:
ConversationSchema.pre('save', function(next) {
this.constructor.findOne({participants: this.participants}, function(err, conversation) {
if (err) next(new Error('Internal error'));
else if (conversation) next(new Error('Conversation exists'));
else next();
});
});

Throwing custom errors from Mongoose pre middleware and using Bluebird promises

I am using Mongoose with Bluebird promises. I am trying to throw a custom error in a validate pre middleware and have it catchable with a Bluebird catch.
Here is my pre validate method
schema.pre('validate', function(next) {
var self = this;
if (self.isNew) {
if (self.isModified('email')) {
// Check if email address on new User is a duplicate
checkForDuplicate(self, next);
}
}
});
function checkForDuplicate(model, cb) {
User.where({email: model.email}).count(function(err, count) {
if (err) return cb(err);
// If one is found, throw an error
if (count > 0) {
return cb(new User.DuplicateEmailError());
}
cb();
});
}
User.DuplicateEmailError = function () {
this.name = 'DuplicateEmailError';
this.message = 'The email used on the new user already exists for another user';
}
User.DuplicateEmailError.prototype = Error.prototype;
I am calling the save with the following in my controller
User.massAssign(request.payload).saveAsync()
.then(function(user) {
debugger;
reply(user);
})
.catch(function(err) {
debugger;
reply(err);
});
This results in the .catch() having an error that looks like this:
err: OperationalError
cause: Error
isOperational: true
message: "The email used on the new user already exists for another user"
name: "DuplicateEmailError"
stack: undefined
__proto__: OperationalError
Is there a way for me to have the custom error be what is delivered to the catch? I want tis so I can check for the error type, and have the controller respond with the appropriate message back in the response.
User.DuplicateEmailError.prototype = Error.prototype;
is wrong, it should be
User.DuplicateEmailError.prototype = Object.create(Error.prototype);
User.DuplicateEmailError.prototype.constructor = User.DuplicateEmailError;
Or better use
var util = require("util");
...
util.inherits(User.DuplicateEmailError, Error);

How to avoid callback hell in nodejs for sequelize

I am using Sequelize and ending up in callback hell:
db.Role.findAll().success(function(roles) {
db.User.findAll({where: {permission: 'coach'}}).success(function(coaches) {
res.render('users/edit_profile', {successFlash: successFlash, user: user, roles: roles, coaches: coaches});
}).error(function(errors) {
console.log("Error", errors);
res.render('dashboard', {successFlash: successFlash, errors: errors});
});
}).error(function(errors) {
console.log("Error", errors);
res.render('dashboard', {successFlash: successFlash, errors: errors});
});
I want to avoid callback hell and make the code more reusable. Is there a way to do this without using async, promises etc?
Suppose I write something like this..
get_user: function(req, res) {
var users = null;
users = func.get_users();
res.render('users/get_users', {users: users});
}
get_users: function() {
db.User.findAll().success(function(users){
return users;
}).error(function(err){
return null;
});
}
The problem with this approach is that, get_user renders the page first and then the callback from get_users is executed, giving me users as always null.
Please help me in this regard!
Thanks in advance!
Sequelize 2.0 ships with native promises, so you don't need to require a promise lib manually in your code. With 2.0 your snippet can be written as:
db.Role.findAll().bind({}).then(function(roles) {
this.roles = roles;
return db.User.findAll({where: {permission: 'coach'}});
}).then(function(coaches) {
res.render('users/edit_profile', {successFlash: successFlash, user: user, roles: this.roles, coaches: coaches});
}).catch(function(errors) {
console.log("Error", errors);
res.render('dashboard', {successFlash: successFlash, errors: errors});
});
You only need a single catch block, because any error is propagated to the top of the chain when you return a promise.
The bind({}) part makes an empty object available inside all functions in your promise chain so you can pass context along in that way. If could also do bind(this), if you want to be able to access something from the outer context, without having to do the idiomatic var self = this
Try this:
get_user: function(req, res) {
var users = null;
func.get_users(function(result){
if (result==null) //handle error
res.render('users/get_users', {users: users});
});
}
get_users: function(callback) {
db.User.findAll().success(function(users){
callback(users);
}).error(function(err){
callback(null);
});
}

express & mongoose - Cannot call method 'get' of undefined - using res.json()

and thanks to be there.
Issue :
I'm making a tiny mongoose "middleware" to handle a mongoose error :
// callback function called at each mongoDB response
var handleDbRes = function(callback) {
return function (err, entries) {
if (err) {
err.status = 500;
return next(err);
}
return callback(entries) // that line throw the exception
}
};
And so I'm using it into an api endpoint, e.g. :
someRouter.get('/', function(req, res) {
models.article.find(handleDbRes(res.json))
})
With that code, I encounter an error :
TypeError: Cannot call method 'get' of undefined
I followed the exception and looked at res.json() declaration, when debugging, I figured out :
var app = this.app;
var *** = app.get('***') // that line throw the exception
I guess that app is not defined cause app doesn't exists in "this".
Please can you help me to solve this problem ? I think that the reason is simple but I don't get it...
Thanks you for listening ;)
EDIT : I tried to res.json.bind(res) and it worked, as I thought, but that's really awful to bind this way for most api endpoint and I guess there is another way to do that kind of functionality without that.
EDIT : Thanks to Mscdex advices, I modified my code this way :
.get('/', function(req, res, next) {
models.article.find(handleDbRes(res.json.bind(res), next))
...
...
// callback function called at each mongoDB response
var handleDbRes = function(successCallback, errorCallback) {
return function (err, entries) {
if (err) {
err.status = 500;
return errorCallback(err);
}
return successCallback(entries)
}
};
When you pass res.json, the context for the json() function is lost (it no longer knows what this is because it is not bound). So here are a few possible solutions:
Use a bound version of the function so that this inside json() will always evaluate correctly:
someRouter.get('/', function(req, res) {
models.article.find(handleDbRes(res.json.bind(res)))
})
Or use a wrapper function instead:
someRouter.get('/', function(req, res) {
function respondJSON(val) {
res.json(val);
}
models.article.find(handleDbRes(respondJSON))
})
Or just pass in res and call res.json() inside handleDbRes():
someRouter.get('/', function(req, res) {
models.article.find(handleDbRes(res))
})
// callback function called at each mongoDB response
var handleDbRes = function(res) {
return function(err, entries) {
if (err) {
err.status = 500;
return next(err);
}
res.json(entries);
}
};
The other problem is that handleDbRes() doesn't have access to next, so you need to also pass that function in for when you run into an error.

Error "Undefined is not a function " Using callback node.JS

I am trying to save a new Document (user) in my MongoDb and I use callback. The code runs and goes until save the user, but after that I get an error.So I can save user. I have the following code:
function saveUser(userName, socialMediaType, socialMediaID, setDocNumber, callback){
var user;
if(socialMediaType == "fbUID"){
user = new users({
userName: userName,
userEmail: 'userEmail',
teams:[],
fbUID : socialMediaID
});
}else
if(socialMediaType =="google"){
//do the same
}
var query = {}
query["'"+ socialMediaType +"'" ] = socialMediaID
users.findOne(query, function(err, userFound){
if (err) { // err in query
log.d("Error in query FoundUser", err)
log.d("User Found", userFound)
}else
if(userFound == undefined){ //if user does not exist
user.save(function(err, user){
if(err) return console.error(err);
log.d("user saved", user);
currentSession = sessionOBJ.login(user._id, socialMediaID);
callback(currentSession,"created")
});
}else{
currentSession = sessionOBJ.login(userFound._id, socialMediaID);
callback(currentSession,"logged")
}
});
}
I call the function above through this code:
f(fbUID !== undefined){
userModelOBJ.saveUser(userName,"fbUID", fbUID, function(currentSession, status) {
res.send({"status":status,
"sessionID": currentSession.sessionID,
"expires" : currentSession.date});
});
I am getting this error :
The error is in the line :
callback(currentSession,"created")
What could be the problem?
I already did many researchers but this is a specific case.
Your saveUser() call is missing the setDocNumber argument. It looks like you're not using it in your code though, so you might be able to safely remove it. If you are using it somewhere else (that you haven't shown) then you need to do some argument checking at the top of saveUser() to support optional arguments.

Resources