Behaviour of async/await in Node.js - node.js

Given the following Node.js express route:
router.route('/user')
.post(async function(req, res) {
if(req.body.password === req.body.passwordConfirm) {
try {
var response = await userManager.addUser(req.body);
res.status(201).send();
} catch(err) {
logger.error('POST /user failed with error: '+err);
res.status(500).send({err:"something went wrong.."});
}
} else {
res.status(400).send({err:'passwords do not match'});
}
})
and the corresponding function in userManager:
var userManager = function() {
this.addUser = async function(userobject) {
userobject.password_hash = await genHash(userobject.password_hash);
var user = new User(userobject);
return await user.save();
};
};
module.exports = userManager;
Can I safely assume that if the code in the catch block does not run, there were no errors? Since the userManager.addUser and everything inside it return promises, which if rejected, should be caught by the catch block.
Or did I miss something and should I still check the response for validity?

Yes, you can safely assume all errors from await userManager.addUser(req.body) are caught.
If the promise returned is rejected with value x, then the await keyword ensures it'll throw x.
But that doesn't ensure there are no errors outside that function call, like in your catch.
Best practice:
post() doesn't expect an async callback function, so it'll ignore your implicitly returned promise.
So when you pass it one, wrap try/catch around everything, since there's no-one to pass up to:
router.route('/user').post(async function(req, res) {
try {
if (req.body.password === req.body.passwordConfirm) {
try {
var response = await userManager.addUser(req.body);
res.status(201).send();
} catch (err) {
logger.error('POST /user failed with error: '+err);
res.status(500).send({err:"something went wrong.."});
}
} else {
res.status(400).send({err:'passwords do not match'});
}
} catch (err) {
logger.error('POST /user fatal error: '+err);
}
})
This way all programming errors are caught and never end up silenced or as unhandled rejections.
Or better, if it's an option, write a post that expects an async function.

Related

How to properly log erros in my application using ExpressJS

I am part of a project which uses nodeJS + ExpressJS for the backend application, and We have a middleware function to log accesses on routes in the database.
When an User tries to access the /user route with a post method, a middleware receives the Request, get information like the URL, ip address, origin, a description of the event and record it in the database.
Everything works just fine, but some of my teammates were discussing about how to log the erros also in the database.
I will put bellow a code example
const create = (request, response) => {
try {
const user = request.body;
const userExists = await usersRepository.findOne({ where: { email } });
if(userExists) {
return response.status.json({ error: 'E-mail already in use' });
}
const creadtedUser = await usersRepository.create(user);
return response.status(200).json({ user: creadtedUser });
} catch (error) {
response.status(500).json({ error });
}
};
When we were discussing about how to implement it, we realized we'd have to call a log error function in a lot of places since we have many flows which leads to an error response.
So the code would be just like:
const create = (request, response) => {
try {
const user = request.body;
const userExists = await usersRepository.findOne({ where: { email } });
if(userExists) {
function() // here we would log the error
return response.status.json({ error: 'E-mail already in use' });
}
const creadtedUser = await usersRepository.create(user);
return response.status(200).json({ user: creadtedUser });
} catch (error) {
function() // here we would log the error
response.status(500).json({ error });
}
};
is it a properly way of dealing with error logging or is there any better way of doing it? Thank you for reading!
You can use the built-in error handler provided by Express.JS for this kind of logic, of course it requires a bit of setup. Like most things in Express.JS, the error handler it's just a middleware function with four parameters err, req, res and next, which MUST be placed after all your other middlewares. It comes to play when, inside a router handle (for example), your call next(err) (where err it's an Error) or by simply throwing err. Check out the documentation for more.
app.use(...)
app.use(...)
app.use((req, res, next) => {
if (req.params.id === undefined) {
let error = new Error("ID required.")
error.statusCode = 400
error.statusMessage = "Request not valid, ID not found."
throw error;
} else {
// Do some stuff...
}
})
// NOTE: After ALL your other middlewares
app.use((err, req, res, next) => {
console.error(err)
res
.status(err.statusCode)
.json(err.statusMessage)
})
Ideally you should log the errors only inside the catch block. Whenever you encounter an error just throw a new error by calling throw new Error("Type your error message here"). Then your function inside catch block will log and handle the error appropriately.
I would change your code to this:
const create = (request, response) => {
try {
const user = request.body;
const userExists = await usersRepository.findOne({ where: { email } });
if(userExists) {
throw new Error("E-mail already in use")
}
const creadtedUser = await usersRepository.create(user);
return response.status(200).json({ user: creadtedUser });
} catch (error) {
function() // log your error
response.status(500).json({ error.message });
}
};
Read more about Errors here.

Conditionally send responses in an Express app

I'm curious whether you can write if statements in an Express app to conditionally execute your code without providing else statements.
if(pred) {
doSomething()
}
return foo;
calcBar(); // doesn't run.
Above is the synchronous code that stops execution after the return statement.
My Express function looks like this:
app.get('/matches', async function(req, res) {
try {
const data = await someGraphQLCall();
if(data.length === 0) {
res.json({ message: "No data." });
}
const someOtherData = await someOtherGraphQLCall(data.foo);
res.json({ someOtherData });
} catch (err) {
res.json({err})
}
}
I know because of this question that code after the first res.json might still be executed. Is there a way to stop that? I don't want the second GraphQL call to execute if the first if condition is met. Is that possible without using else ?
Edit:
As the question I linked above mentioned, using a return statement is a bad option because:
it also makes it less meaningful and vague, cause it uses incorrect semantics. If you are not using the value from the function, then you shouldn't return one.
You can use return keyword on the first response to immediately return from the function.
app.get('/matches', async function(req, res) {
try {
const data = await someGraphQLCall();
if(data.length === 0) {
return res.json({ message: "No data." });
}
const someOtherData = await someOtherGraphQLCall(data.foo);
res.json({ someOtherData });
} catch (err) {
res.json({err})
}
}
Edit:
As an alternative, you can split the logic of the data and building up response. This way you can use return and it's easier to read:
app.get('/matches', async function (req, res) {
try {
const data = await getDataFromGraphQLCall();
res.json(data);
} catch (err) {
res.json({ err })
}
});
async function getDataFromGraphQLCall() {
const data = await someGraphQLCall();
if (data.length === 0) {
return { message: "No data." };
}
const someOtherData = await someOtherGraphQLCall(data.foo);
return { someOtherData };
}
If you are wondering if there is a way to achieve that without the else, yes it is.
But, It might not be THE cleanest way. IMO, using return is the best way to stop the execution of the controller.
Anyways, You can split the chunk of code into middlewares and use ternary operator to conditionally send responses.
In your example, separate out data = await someGraphQLCall(); as follows:
const middlewareOne = async function(req, res, next) {
let data = [];
let response = { message: "No data." };
try {
data = await someGraphQLCall();
req.locals.data = data; // <- attach the data to req.locals
} catch (err) {
response = { err };
}
data.length === 0 ? res.json(response) : next();
};
And then, mount the middlewareOne BEFORE your controller:
app.get("/matches", middlewareOne, async function controller(req, res) {
try {
const someOtherData = await someOtherGraphQLCall(req.locals.data.foo);
res.json({ someOtherData });
} catch (err) {
res.json({ err });
}
});
How this works is, the controller function would only be executed by express if the next() is called from the previous middleware -- middlewareOne in the example.
And as middlewareOne only calls next() if the data.length is not 0, it would work as you expected.
For more information on passing data from one middleware to other, read this
The return statement terminates the function execution in this context. In my opinion, you should handle the success case then the error case since the code will be read top to bottom.
In if statement, data could be undefined or null.
You can read more here: MDN - return
app.get('/matches', async function(req, res) {
try {
const data = await someGraphQLCall();
// alternative, if (data && data[0]) {
if (data && data.length) {
const someOtherData = await someOtherGraphQLCall(data.foo);
return res.json({ someOtherData });
}
return res.json({ message: "No data." });
} catch (err) {
console.log(err); // log error with logger and drain to loggly.
res.json({ err })
}
}
With Void operator:
Void operator allows you to return undefined but evaluate the given expression.
You can read more here: MDN - Void
app.get('/matches', async function(req, res) {
try {
const data = await someGraphQLCall();
// alternative, if (data && data[0]) {
if (data && data.length) {
const someOtherData = await someOtherGraphQLCall(data.foo);
return void res.json({ someOtherData });
}
return void res.json({ message: "No data." });
} catch (err) {
console.log(err); // log error with logger and drain to loggly.
res.json({ err })
}
}

Async function throws error - yet express endpoint that calls it - does not see it

I have a helper function that hits an API and fetches a page by ID. It uses async/await and I am trying to handle errors with try catch.
To test the error handling I am purposefully giving it an ID that does not exist.
Here is the method:
const findPage = async (req, res, pageId) => {
let document
try {
let response = await getByID(pageId)
if (!response) throw Error('No Response')
return document
} catch (error) {
console.log(error) // I can see the error is being thrown.. I am purposefuly giving it an id that does not exist
return error
}
}
It does indeed throw an error like I expect. However, I am calling the function in another part of the app using an express Route.
Router.route('/page/:id').get(async (req, res) => {
let results
try {
results = await findPage(req, res, req.params.id) // This Function Returns an error
// Yet we still get results
res.json({results, msg: 'WHY?'})
} catch (error) {
res.send(error)
}
})
In the same Router file, I have attempted to add some specific middleware to this Router as well, but as there is no error, it is never triggered.
Router.use((err, req, res, next) => {
if (err) {
console.log('holy error')
} else {
console.log('no error')
}
next(err)
})
How can the express API call return results, and not the error, when the function it is calling itself returns an error?
What I can see from your code is that your try/catch block inside the Router.route is not catching an expetion of findPage function, and that is becasue you are also catching an exception inside that findPage and just simply returning an error rather than throwing an exception;
try {
results = await findPage(req, res, req.params.id) // This Function Returns an error
// Yet we still get results
res.json({results, msg: 'WHY?'})
} catch (error) {
res.send(error)
}
So inside findPage if you really need to do something when excptions happening then you have to catch it and if you like the calller also catch the expetion you need to throw the same erro or a new more context-aware error again. Otherwise if you dont do any action when exception happens you dont need to catch it.
const findPage = async (req, res, pageId) => {
let document
try {
let response = await getByID(pageId)
if (!response) throw Error('No Response')
return document
} catch (error) {
// do something here if you really need to otherwise you dont need to catch exceptions here
// then rather than `return error` you should
throw error
}
}
You are catching the error in findPage() which means the error won't propagate up the call stack. You are just returning the error like a normal value, which will end up in the variable results in your Routes function. If you want to deal with the error in both places you need to throw it again in findPage():
async function callee(){
try {
throw("Some Error")
} catch(err) {
console.log("caught error in callee:", err)
throw(err)
}
}
async function caller(){
try {
let val = await callee()
console.log("returned value", val)
} catch (err) {
console.log("caught error in caller: ", err)
}
}
caller()
If you don't want to deal with it on both places, catch it in the function that's responsible for handling errors:
async function callee(){
throw("Some Error")
}
async function caller(){
try {
let val = await callee()
console.log("returned value", val)
} catch (err) {
console.log("caught error in caller: ", err)
}
}
caller()

Error: Callback was already called in loopback

I have the following code:
"use strict";
const Raven = require("raven");
Raven.config(
"test"
).install();
module.exports = function(Reservation) {
function dateValidator(err) {
if (this.startDate >= this.endDate) {
err();
}
}
function sendEmail(campground) {
return new Promise((resolve, reject) => {
Reservation.app.models.Email.send(formEmailObject(campground),
function(
err,
mail
) {
if (err) {
console.log(err);
Raven.captureException(err);
reject(err);
} else {
console.log(mail);
console.log("email sent!");
resolve(mail);
}
});
});
}
function formEmailObject(campground) {
return {
to: "loopbackintern#yopmail.com",
from: "noreply#optis.be",
subject: "Thank you for your reservation at " + campground.name,
html:
"<p>We confirm your reservation for <strong>" +
campground.name +
"</strong></p>"
};
}
Reservation.validate("startDate", dateValidator, {
message: "endDate should be after startDate"
});
Reservation.observe("after save", async function(ctx, next) {
try {
const campground = await Reservation.app.models.Campground.findById(
ctx.instance.campgroundId
);
const mail = await sendEmail(campground);
next();
} catch (e) {
Raven.captureException(e);
next(e);
}
});
};
Sorry for the poor formatting. When the flow is done I get this error:
(node:3907) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: Callback was already called.
I am calling the next() callback in two places, one in the try code and one in the catch code. I assume that when it all goes right, next callback is called only once, and the same when it goes wrong. But it seems that it is called twice and I don't know why.
I also tried to call next outside the try/catch code but it results in the same error. If I left only the next that is called inside the catch code it doesn't throw the error.
Any idea? Thanks!
if you are using async function you shouldn't explicitly call next, it gets automatically called.
check out this github issue for loopback async/await
so your hook can be like the following.
Reservation.observe("after save", async ctx => {
try {
const campground = await Reservation.app.models.Campground.findById(
ctx.instance.campgroundId
);
const mail = await sendEmail(campground);
} catch (e) {
Raven.captureException(e);
throw e;
}
});
NB: you don't need to wrap it in try catch unless you want to modify/work with the error.
You should declare your sendEmail method as async as it returns a promise.
async function sendEmail(campground) {
...
}
After reading this article, I created a await-handler.js file which include following code.
module.exports = (promise) =>
promise
.then(data => ({
ok: true,
data
}))
.catch(error =>
Promise.resolve({
ok: false,
error
})
);
Then in MyModel.js file, I created a async function to get a value from database as follow.
const awaitHandler = require("./../await-handler.js")
const getMaxNumber = async (MyModel) => {
let result = await awaitHandler(MyModel.find());
if (result.ok) {
if (result.data.length) {
return result.data.reduce((max, b) => Math.max(max, b.propertyName), result.data[0] && result.data[0].propertyName);
} else {
return 0;
}
} else {
return result.error;
}
}
As per #Mehari's answer, I've commented call to next() method as follow:-
module.exports = function(MyModel) {
MyModel.observe('before save', async(ctx, next) => {
const maxNumber = await getMaxNumber (MyModel);
if(ctx.instance) {
...
set the required property using ctx.instance.*
like createdAt, createdBy properties
...
// return next();
} else {
...
code for patch
...
// return next();
}
})
}
This solves the warning issue whenever saving endpoint is triggered.
But the warning issue still appear when I run the endpoint to load the resource.Like
http://localhost:3000/api/MyModel
Previously, the issue appear only when the before save operation hook gets triggered.
After encountering this issue, I checked adding access and loaded operation hooks and I found that the the warnings are issued after loaded operation hook.
MyModel.observe('access', (ctx, next) => {
return next();
})
MyModel.observe('loaded', (ctx, next) => {
return next();
})
What could have caused this issue and how can it gets resolved?

Proper error handling using async/await in Node.js

I have the following code in my Node.js express app:
router.route('/user')
.post(async function(req, res) {
if(req.body.password === req.body.passwordConfirm) {
try {
var response = await userManager.addUser(req.body);
res.status(201).send();
} catch(err) {
logger.error('POST /user failed with error: '+err);
res.status(500).send({err:"something went wrong.."});
}
} else {
res.status(400).send({err:'passwords do not match'});
}
})
and userManager:
var userManager = function() {
this.addUser = async function(userobject) {
userobject.password_hash = await genHash(userobject.password_hash);
var user = new User(userobject);
return await user.save();
};
};
module.exports = userManager;
My question is: Will the try catch block in the route catch all errors thrown in addUser or will it only catch the ones that are thrown by user.save(), since that is the one that gets returned?
The answer is yes, it will catch all the errors inside try block and in all internal function calls.
async/await is just syntax sugar for promises. Thus if something is possible using promises then it is also possible using async/await.
For example both of the following code snippets are equivalent:
Using promises:
function bar() {
return Promise.reject(new Error('Uh oh!'));
}
function foo() {
return bar();
}
function main() {
return foo().catch(e => {
console.error(`Something went wrong: ${e.message}`);
});
}
main();
Using async/await:
async function bar() {
throw new Error('Uh oh!');
}
async function foo() {
await bar();
}
async function main() {
try {
await foo();
}
catch(e) {
console.error(`Something went wrong: ${e.message}`);
}
}
main();
In fact your code will not work since you don't use await on userManager.addUser.
It also forces you to use async on the parent function and that may break things up. Check express documentation (or just try if it works).
router.route('/user')
.post(async function(req, res) {
if(req.body.password === req.body.passwordConfirm) {
try {
var response = await userManager.addUser(req.body);
res.status(201).send();
} catch(err) {
logger.error('POST /user failed with error: '+err);
res.status(500).send({err:"something went wrong.."});
}
} else {
res.status(400).send({err:'passwords do not match'});
}
})

Resources