I'm pretty new on NodeJS and I'm still trying to figure out how to handle errors. I read a lot of questions but I don't realize what I'm doing wrong.
Here I have a login function:
export const login = async (req, res) => {
let body = req.body;
try {
const user = await User.findOne({ username: body.username });
if (!user) {
throw new InternalError("Username or password are wrong");
}
if (!bcrypt.compareSync(body.password, user.password)) {
throw new InternalError("Username or password are wrong");
}
let token = jwt.sign({ data: user }, "secret", {
expiresIn: 60 * 60 * 24 * 30
});
return res.json({
user: user,
token: token
});
} catch (error) {
throw new GenericError(error);
}
};
And this is the error I get if, for example, I include a wrong password:
(node:12332) UnhandledPromiseRejectionWarning: GenericError: Username or password are wrong
(node:12332) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by
throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch()
I know the promises made with Await must have a .then() and .catch() but this is not an error on executing the promise but on validating the password.
What I want is to throw the error and get the response. Right now the request never ends and the previous error is displayed.
Thank you!
PS: InternalError and GenericError are just errors created by me which extends from Error
You receive the warning from node because you are re-throwing a GenericError after catching InternalError. Instead of re-throwing the GenericError, you should return your response or catch your GenericError when calling login.
Here is your code modified for both.
export const login = async (req, res) => {
// We get the body info
let body = req.body;
try {
// We find the user with the username from the body
const user = await User.findOne({ username: body.username });
// Let's assume no user exists, so we throw an InternalError,
// This skips straight to the catch block.
if (!user) {
throw new InternalError("Username or password are wrong");
}
// We never reach these two statements
// because of the error above being thrown.
if (!bcrypt.compareSync(body.password, user.password)) {
throw new InternalError("Username or password are wrong");
}
let token = jwt.sign({ data: user }, "secret", {
expiresIn: 60 * 60 * 24 * 30
});
res.json({
user: user,
token: token
});
} catch (err) {
// We caught our InternalError from not having a user above.
// Now we should return a response that the user is invalid.
// I am just going to return the error that we previously threw.
res.json({
error: err
});
}
};
You can definitely throw the GenericError from your catch block. However, doing that then requires you to catch the GenericError wherever you are calling your login function.
export const login = async (req, res) => {
// We get the body info
let body = req.body;
try {
// We find the user with the username from the body
const user = await User.findOne({ username: body.username });
// Let's assume no user exists, so we throw an InternalError,
// This skips straight to the catch block.
if (!user) {
throw new InternalError("Username or password are wrong");
}
// We never reach these two statements
// because of the error above being thrown.
if (!bcrypt.compareSync(body.password, user.password)) {
throw new InternalError("Username or password are wrong");
}
let token = jwt.sign({ data: user }, "secret", {
expiresIn: 60 * 60 * 24 * 30
});
res.json({
user: user,
token: token
});
} catch (err) {
// Throw the generic error
throw GenericError(err);
}
};
try {
// We call the login function that is defined above.
// It is going to throw a GenericError, so we have to
// catch it.
await login(req, res);
} catch (err) {
// Now we need to catch the generic error
res.json({
error: err
});
}
Related
exports.createUser = functions.https.onCall(async (data, _context) => {
const email = data.email;
const password = data.password;
try {
// First, create the user account.
const userRecord = await admin.auth().createUser({
email: email,
password: password,
});
// User successfully created, now update the database.
const userId = userRecord.uid;
const db = admin.firestore();
const batch = db.batch();
batch.create(
db.collection("userAccounts").doc(userId), {created: admin.firestore.FieldValue.serverTimestamp()},
);
await batch.commit();
// Database successfully updated, now return the newly-created userId.
return Promise.resolve(userId);
} catch (error) {
if (userId) {
// There was an error updating the database. However, a user was created
// beforehand. Therefore, delete the user before terminating the
// function.
admin.auth().deleteUser(userId); // <-- this throws
throw new functions.https.HttpsError("unknown", "Database error", error);
}
throw new functions.https.HttpsError("unknown", "Authentication error", error);
}
});
This function performs two async tasks: creates a user and updates a database. If the database update fails, I need to undelete the user. This function deletes the user in the catch block when it catches a database error. However, deleting the user itself throws. If the delete-user function fails, would that error be caught in this catch block and possibly create an infinite loop? How best to handle this scenario?
It will not create an infinite loop, but the error won't be caught either - which should be avoided. You'll need to tack on another .catch, because you also want to throw a different error afterwards.
} catch (error) {
if (userId) {
// There was an error updating the database. However, a user was created
// beforehand. Therefore, delete the user before terminating the
// function.
admin.auth().deleteUser(userId)
.catch(() => {}); // Ignore errors thrown by deleteUser
throw new functions.https.HttpsError("unknown", "Database error", error);
}
throw new functions.https.HttpsError("unknown", "Authentication error", error);
}
You should also be sure that the caller of this callback passed to onCall catches its asynchronous errors to avoid unhandled rejections.
Still, this process is pretty suspect - it seems odd to be deliberately calling a function you think will probably throw. If you need to call deleteUser only in the case that createUser succeeds, checking for the userId looks like the right approach, but that'll only work if the variable is in scope at that point, which it isn't. You can change your code to:
exports.createUser = functions.https.onCall(async (data, _context) => {
const { email, password } = data;
let userId; // <---------------------------
try {
// First, create the user account.
const userRecord = await admin.auth().createUser({ email, password });
// User successfully created, now update the database.
userId = userRecord.uid; // <---------------------------
const db = admin.firestore();
const batch = db.batch();
batch.create(
db.collection("userAccounts").doc(userId), {created: admin.firestore.FieldValue.serverTimestamp()},
);
await batch.commit();
// Database successfully updated, now return the newly-created userId.
return userId; // don't need to wrap this in a Promise
} catch (error) {
if (userId) {
// There was an error updating the database. However, a user was created
// beforehand. Therefore, delete the user before terminating the
// function.
admin.auth().deleteUser(userId)
.catch(() => {}); // This SHOULDN'T throw, but just in case
throw new functions.https.HttpsError("unknown", "Database error", error);
}
throw new functions.https.HttpsError("unknown", "Authentication error", error);
}
});
Or to
exports.createUser = functions.https.onCall(async (data, _context) => {
const { email, password } = data;
let userId;
try {
// First, create the user account.
const userRecord = await admin.auth().createUser({ email, password });
userId = userRecord.uid;
} catch (error) {
throw new functions.https.HttpsError("unknown", "Authentication error", error);
}
try {
// User successfully created, now update the database.
const db = admin.firestore();
const batch = db.batch();
batch.create(
db.collection("userAccounts").doc(userId), { created: admin.firestore.FieldValue.serverTimestamp() },
);
await batch.commit();
// Database successfully updated, now return the newly-created userId.
return userId;
} catch (error) {
// There was an error updating the database. However, a user was created
// beforehand. Therefore, delete the user before terminating the
// function.
admin.auth().deleteUser(userId);
throw new functions.https.HttpsError("unknown", "Database error", error);
}
});
I have the following function:
exports.signup = async(req, res) => {
console.log('signup');
const user = new User({
username: req.body.username,
email: req.body.email,
password: bcrypt.hashSync(req.body.password, 8)
});
try {
if (await user.save()) {
if (isNonEmptyArray(req.body.roles)) {
// How do I catch this error? can be a role error or db error
const roles = await Role.find({name: { $in: req.body.roles }}).exec()
user.roles = roles.map(role => role._id);
if (await user.save()) {
success(res, 'Registered!');
}
} else {
// How do I catch this error? can be a role error or a db error
const role = await Role.findOne({name: 'user'}).exec();
user.roles = [role._id];
if (await user.save()) {
success(res, 'Registered!');
}
}
}
} catch(error) {
fail(res, {message: 'Database internal error occured.'});
}
};
Is it correct that the catch will trigger for all errors in the block including calls to await Role.find({name: { $in: req.body.roles }}).exec()? How would I catch this error independently? Do I need to add a try and catch within the try and catch statement?
like you said you can use another try-catch block to distinguish which error are you catching.
try {
const role = await Role.findOne({name: 'user'}).exec();
} catch(err) {
console.log(err);
}
Another idea might be to use the promises and catch the error in each .catch segment, for example
var query = Role.findOne({name: 'user'});
query.exec().then(function () {
// handle success
}).catch(function (err) {
// handle error
});
anyway there are some important to feature to keep in mind when using async/await and try-catch block, I'll put here the conclusion of an article and the link to it if you are interested:
conclusion:
We can use try...catch for synchronous code.
We can use try...catch (in combination with async functions) and the .catch() approaches to handle errors for asynchronous code.
When returning a promise within a try block, make sure to await it if you want the try...catch block to catch the error.
Be aware when wrapping errors and rethrowing, that you lose the stack trace with the origin of the error.
font: https://itnext.io/error-handling-with-async-await-in-js-26c3f20bc06a
I have an express route for handling password resets, and with that i have a route, where i first find the user, and have some error handling with that, but now i want aditional error handling inside a nested function, and I'm not sure what pattern to use
function triggerPasswordResetEmailSend(req, res, next) {
var email = req.body.email;
if (!email) return res.status(422).json({error: "Please provide an email."});
UserRepositoryClass.findUserByEmail(email).then(user =>{
if(!user) return res.status(422).json({message: "User not found"})
sendPasswordReset(user);
return res.status(200).json({user: user});
}).catch(err =>{
return res.status(500).json({error: err})
});
}
Inside this function i do some initial error handling. The issue now is that the sendPasswordReset function can also throw errors, but there are not caught by the .catch() function, so I'm looking for something to handle this function.
I have tried passing the req and res objects into the function, but that does not seem like a good solution. I could do some try catch or maybe return a promise. But i want to ensure, that i follow the same pattern and best practises as i have already tried to do.
Here is the code snippet from my mail function:
module.exports = (user) => {
const userResetToken = generatePasswordToken();
UserRepositoryClass.setPasswordResetToken(user.id, userResetToken);
const passwordResetUrl = PASSWORD_RESET_URL(user._id, userResetToken);
return sendMail(options(user.email, passwordResetUrl));
}
You can use promise instead of function.
module.exports.sendPasswordReset = user = new Promise((resolve, reject) => {
const userResetToken = generatePasswordToken();
UserRepositoryClass.setPasswordResetToken(user.id, userResetToken);
const passwordResetUrl = PASSWORD_RESET_URL(user._id, userResetToken);
sendMail(options(user.email, passwordResetUrl))
.then(response => {
resolve(response, null); // we can get result as (data,error) here error is null
})
.catch(err => {
reject(null, err); // here response is null
});
});
You can use sendPasswordReset Promise like this:
sendPasswordReset(user).then((res, err) => {
// here you can get your res as well as err
if (err) throw new Error("Error while sending an email");
console.log("response", res);
});
In a backend API I have a login route which should perform the following sequence of actions:
Given an username and password, try to authenticate the user against an Active Directory. If authentication has failed reply with status 401. If success, continue.
Look for an user with the given username in the database. If not found reply with status 403, otherwise continue.
Find if the user document has some details like email, display name, etc (in case this is not the first time logging in). If yes reply with the user object, otherwise continue.
Get user details from the Active Directory and update the user object in the database. Reply with the updated object.
Code:
router.post('/login', (req, res, next) => {
// capture credentials
const username = req.body.username;
const password = req.body.password;
let user = null;
// authenticate
ad.authenticate(username, password)
.then((success) => {
if (!success) {
res.status(401).send(); // authentication failed
next();
}
return User.findOne({ username }).exec();
})
.then((found) => {
if (!found) {
res.status(403).send(); // unauthorized, no account in DB
next();
}
user = found;
if (user.displayName) {
res.status(201).json(user); // all good, return user details
next();
}
// fetch user details from the AD
return ad.getUserDetails(username, password);
})
.then((details) => {
// update user object with the response details and save
// ...
return user.save();
})
.then((update) => {
res.status(201).json(update); // all good, return user object
next();
})
.catch(err => next(err));
});
Now I had this running with callbacks but it was really nested. So I wanted to give Bluebird promises a try, but I have two problems:
Looks chaotic, any better way to chain the calls and handle responses?
Whenever I call next() to stop the request after replying, the execution continues to the other .then(). Although the client receives the correct response, in the server log I find that the execution have continued. For example, if there is no account in DB for a given user, the client receives the 403 response but in the server log I see an exception failed to read property displayName of null, because there was no user and it should have stopped in the next() after res.status(403).send();.
Best use if/else to make clear what branches will execute and which won't:
ad.authenticate(username, password).then((success) => {
if (!success) {
res.status(401).send(); // authentication failed
} else {
return User.findOne({ username }).exec().then(user => {
if (!user) {
res.status(403).send(); // unauthorized, no account in DB
} else if (user.displayName) {
res.status(201).json(user); // all good, return user details
} else {
// fetch user details from the AD
return ad.getUserDetails(username, password).then(details => {
// update user object with the response details and save
// ...
return user.save();
}).then(update => {
res.status(201).json(update); // all good, return user object
});
}
});
}
}).then(() => next(), err => next(err));
The nesting of then calls is quite necessary for conditional evaluation, you cannot chain them linearly and "break out" in the middle (other than by throwing exceptions, which is really ugly).
If you don't like all those then callbacks, you can use async/await syntax (possibly with a transpiler - or use Bluebird's Promise.coroutine to emulate it with generator syntax). Your whole code then becomes
router.post('/login', async (req, res, next) => {
try {
// authenticate
const success = await ad.authenticate(req.body.username, req.body.password);
if (!success) {
res.status(401).send(); // authentication failed
} else {
const user = await User.findOne({ username }).exec();
if (!user) {
res.status(403).send(); // unauthorized, no account in DB
} else if (user.displayName) {
res.status(201).json(user); // all good, return user details
} else {
// fetch user details from the AD
const details = await ad.getUserDetails(username, password);
// update user object with the response details and save
// ...
const update = await user.save();
res.status(201).json(update); // all good, return user object
}
}
next(); // let's hope this doesn't throw
} catch(err) {
next(err);
}
});
To answer your second point, you have to reject your promise after calling next() (or at least return something, otherwise the line after will be executed). Something like
next();
return Promise.reject()
and change your catch so it works if you do not have an error
.catch(err => {
if (err)
next(err)
});
To your second question first: there is no way to break/stop a promise chain, unless your callback throw err like
doAsync()
.then(()=>{
throw 'sth wrong'
})
.then(()=>{
// code here never runs
})
You can simply try below demos to verify the second callback still runs.
doAsync()
.then(()=>{
res.end('end')
})
.then(()=>{
// code here always runs
})
doAsync()
.then(()=>{
return;
})
.then(()=>{
// code here always runs
})
To your first question: to use the second parameter in then(), which means reject. And each time split the logic to two parts.
var p = new Promise(function(resolve, reject) {
return
ad.auth(username, password).then(()={
// check if 401 needed. If needed, return reject
if (dont needed 401 in your logic)
resolve(username)
else
reject({ msg: 'authentication has failed', status: 401 })
})
});
p
.then( (username)=>{
// this only runs when the previous resolves
return User.findOne({ username }).exec()
}, (data)=>{
// in fact in your case you dont even have to have the reject callback
return data
} )
.then( (found)=>{
return
new Promise(function(resolve, reject) {
if (found && /*your logic to determine it's not 403*/)
resolve(user)
else
reject({ msg: 'unauthorized, no account in DB', status: 403 })
})
} )
.then( (found)=>{
return
new Promise(function(resolve, reject) {
if (found && /*your logic to determine it's not 403*/)
resolve(user)
else
reject({ msg: 'unauthorized, no account in DB', status: 403 })
})
} )
.then( (user)=>{
return
new Promise(function(resolve, reject) {
if (/*your logic to determine it has the full info*/)
resolve(user)
else
return ad.getUserDetails(username, password)
})
} )
.then( (user)=>{
// all is good, do the good logic
}, (data)=>{
// something wrong, so here you can handle all the reject in one place
res.send(data)
} )
In my node js app, I'm trying to create a registration route that takes a few asynchronous steps. In an early step, there's a condition where I want to stop and just return a message, but I'm getting confused about mixing conditions into the promises.
My specific question is in comments in the following psuedo-code:
router.post('/register', function(req, res, next) {
var identity = req.body.identity;
var password = req.body.password;
// can't register without a reservation
hasReservation(identity).then(function(hasReservation) {
if (hasReservation) { return register(identity, password); }
else {
// stuck here: I want to say:
res.json({ registered: false, reason: "missing reservation" });
// and then I want to stop further promises
// but this isn't an error, I don't want to throw an error here
}
}).then(function(user) {
// in my app, registering an existing user is okay
// I just want to reset a few things about the user and save
// but I don't want to be here if there's no reservation
if (user) {
user.foo = 'bar';
return user.save().then(function() { return user; });
} else {
return register(identity, password);
}
}).then(function(user) {
// I want to do more asynch stuff here
}).then(function(result) {
res.json(result);
}).catch(function(error) {
res.status(500).json(error);
});
});
How can I conditionally "bail out" after that first promise completes, without throwing an error?
If register(identity, password) returns a promise, you can reorganise your code as follows:
router.post('/register', function(req, res, next) {
var identity = req.body.identity;
var password = req.body.password;
// can't register without a reservation
hasReservation(identity)
.then(function(hasReservation) {
if (hasReservation) {
return register(identity, password)
.then(function(user) {
// in my app, registering an existing user is okay
// I just want to reset a few things about the user and save
// but I don't want to be here if there's no reservation
if (user) {
user.foo = 'bar';
return user.save().then(function() { return user; });
} else {
return register(identity, password);
}
})
.then(function(user) {
// I want to do more asynch stuff here
})
.then(function(result) {
res.json(result);
})
} else {
// stuck here: I want to say:
res.json({ registered: false, reason: "missing reservation" });
// and then I want to stop further promises
// but this isn't an error, I don't want to throw an error here
}
})
.catch(function(error) {
res.status(500).json(error);
});
});
Otherwise, if register(..) just returns a value, wrap the FIRST instance of the register(...) in a Promise.resolve
return Promise.resolve(register(identity, password))