I am having a real difficult time writing some clean code with a simple user registration/login manager. I am trying to stay out of nesting/callback hell, and am also not seeing any benefit in using the supposedly cleaner async/await.
Take for example my user registration code. It must perform 3 promises in sequence
Ensure the user is unique in the database
Hash the password
Insert the user into the database
I want to
Log any errors in each individual promise call (ie db failure)
Log the final result in the highest level function (ie register successful)
Send back an HTTP response from the caller.
It looks like this right now
hashPassword(password){
let self = this;
return bcrypt.hash(password, 10)
.then(function(hash) {
return hash;
})
.catch(function(err){
self.logger.error('failed to hash password');
self.logger.error(err);
return Promise.reject(err)
});
}
insertUser(email, passwordHash){
let self = this;
let data = {
email: email,
password: passwordHash
};
return this.collection.insertOne(data)
.then(function(res) {
self.logger.info(`inserted ${email} into db`);
return res;
})
.catch(function(err){
self.logger.error('failed to add user to db');
self.logger.error(err);
return Promise.reject(err)
});
}
findUser(email){
let self = this;
return this.collection.findOne({email:email})
.then(function(element) {
if(!element){
self.logger.info(`user ${email} not found`);
}
return element;
})
.catch(function(err){
self.logger.error(`failed to query user ${email}`);
self.logger.error(err);
return Promise.reject(err);
});
}
registerUser(email, password){
let self = this;
return this.findUser(email)
.then(function(element){
if(element){
self.logger.error(`user ${email} already exists`);
// now what do i do here for the caller?
// reject the promise? resolve with an err code?
return Promise.reject()
}
// what if this one fails? how will the caller know..
return self.hashPassword(password);
})
.then(function(hash){
return self.insertUser(email, hash);
})
.then(function(){
self.logger.info(`registered user ${email}`);
})
.catch(function(err){
self.logger.error('failed to register user');
return Promise.reject(err)
});
}
I am actually super comfortable with the base functions (findUser
, hashPassword
, insertUser
). But the registerUser
barely makes sense. The fact that you return promises within a then
call really bothers me, for one, but that's just the way she goes. The real issue here is that, if any of the three calls in the process fails, I'm not sure how to tell my caller which thing failed.
Here is the caller.
router.post('/register', function(req, res, next){
if(req.body.email && req.body.password && req.body.confirm){
userManager.registerUser(req.body.email, req.body.password)
.then(function(result){
res.sendStatus(200);
})
.catch(function(err){
res.sendStatus(400);
});
}
else {
res.sendStatus(400);
}
});
Now I tried to do everything in async/await, but that just requires lots of nest try/catches which I think are even worse. I am just lost here trying to keep this clean.
.then(function(hash) {return hash;})