Lets look at the below modified Restify example which now includes Node 7/8s async/await support.
I have slight concerns on the proper implementation of this into express/restify/etc. My concern is with the promise hanging in the event loop for longer than it needs to... I know that this isn't exactly a promise, however should I be concerned about this implementation? I have yet to notice any issues.
'use strict';
const restify = require('restify');
const User = require('./models/User');
const server = restify.createServer({
name: 'myapp',
version: '1.0.0'
});
server.use(restify.acceptParser(server.acceptable));
server.use(restify.queryParser());
server.use(restify.bodyParser());
server.get('/echo/:name', async function (req, res, next) {
try {
const user = await User.findOne({
name: req.params.name;
});
res.send(user.get({plain: true}));
} catch (error) {
console.error(error);
res.send(500);
}
return next();
});
server.listen(8080, function () {
console.log('%s listening at %s', server.name, server.url);
});
There is a problem with using async function instead of regular functions accepting callbacks as the errors are handled differently.
In callback functions (aka "err-backs") the callback must be called regardless if the execution was successful. The first parameter is to be an error object.
The async function simply returns a rejected promise in case of any error (synchronous or asynchronous).
So by default, the Express.js/Restify expect a regular err-back. And if you pass the async function instead and it fails the Express.js/Restify will keep waiting for a callback to be called ignoring the rejected promise. It's simply not aware of a returned promise and not handling it.
Finally, the callback won't be called at all and the endpoint will timeout.
So you won't be able to handle the error properly.
You can try it out:
server.get('/echo/:name', async function (req, res, next) {
throw new Error();
});
So as a rule of thumb, I'd recommend not to mix the concepts and never pass callbacks into async functions. This is a red flag.
In order to fix this you need to use a wrapper like this for example:
const wrap = function(fn) {
return function(req, res, next) {
return fn(req, res, next).catch(function(err) {
return next(err);
});
};
};
server.get('/echo/:name', wrap(async function (req, res, next) {
throw new Error();
}));
You will get a proper status code and there will be no timeout anymore.
There are also a couple of modules you can use if you don't want to wrap it yourself:
Express.js: express-async-wrap
Restify: restify-async-wrap
Related
I am currently working on node.js + express + mongoDB project. I am trying to handle error that occurs when data cannot be received from database. I am simulating this by terminating mongod process in console and calling .get in Postman. Sadly instead of getting an error in Postman I only get Unhandled Promise Rejection in console. I read a lot of posts about error handling and implemented it according to this guide: https://expressjs.com/en/guide/error-handling.html. I would be grateful for any idea of how can I fix this.
The code:
Printing all courses:
router.get("/", async (req, res, next) => {
try {
const courses = await Course.find().sort("dishName");
res.send(courses);
} catch (ex) {
next(ex);
}
});
error.js:
module.exports = function (err, res, req, next) {
res.status(500).send(`500 Error`);
};
index.js
const error = require(`./middleware/error`);
app.use(error);
app.use(error) is placed as the last app.use
There is a minor mistake in your code. The order of the req and res parameters in the error handler function should not be changed.
// Error.js
module.exports = function (err, req, res, next) {
res.status(500).send(`500 Error`);
};
I'm trying to setup an express app to catch any throw new error from a centeral function instead of many try and catch
var app = express();
tenantsRouter.get('/my_endpoint', async function(req, res, next) {
var result = await methodThatCouldFail()
res.status(HttpStatus.OK).json({result);
});
app.use(apiPrefix + '/tenants', tenantsRouter);
error_handler = function(err, req, res, next) {
console.error(`general error catcher - ${err}.`)
return res.status(HttpStatus.BAD_REQUEST).json({
'error': 'we are on it.'
})
}
// error handler
app.use(error_handler)
The thing is, unless I use specific try and catch in my_endpoint the error_handler doesn't catch throw new Error inside the methodThatCouldFail();
The only API that Express provides to pass errors down the chain of handlers is via the next() function. You need to wrap it around something that will do the try/catch and call next() for you:
function asyncHandler (f) {
return function (req, res, next) {
f(req, res, next).catch(next)
}
}
Now you can do:
tenantsRouter.get('/my_endpoint', asyncHandler(async function(req, res, next) {
var result = await methodThatCouldFail()
res.status(HttpStatus.OK).json({result);
}));
Which would work exactly how you expected it to work.
There are actually several implementations of this simple module on npm if you don't feel like writing it yourself including this one: https://www.npmjs.com/package/express-async-handler
I upgraded to Express 4 and have the following problem with error handling.
Before I used to have the code in app.js — after all the possible routes I had
var routes = require('./routes')
app.use(routes.notfound)
app.use(routes.error)
app.use(routes.badrequest)
And then inside the /routes/index.js I had:
exports.notfound = function(req, res) {
res.status(404).format({
html: function() {
res.render('404')
},
json: function() {
res.send({
message: 'We did not find what you were looking for :(',
})
},
xml: function() {
res.write('<error>\n')
res.write(
' <message>We did not find what you were looking for :(</message>\n'
)
res.end('</error>\n')
},
text: function() {
res.send('We did not find what you were looking for :(\n')
},
})
}
Now when I call for 404 elsewhere in the app (not in app.js) using res.send(404) I get the right 404 code response but I don't get to the part where it selects whether it shows html or json or text.
How do I do that?
You need to handle error catching differently, here is one way to do so:
Create a middleware after all of your routes that will catch errors you pass to it, the callback would take in an extra parameter containing details about the error:
app.use((err, req, res, next) => {
// Handle the error here
});
Whenever you want to render an error, you can use next in your routes to pass it to this middleware, and pass extra information you can use to decide how to handle the error. There is a module called http-errors that can create objects like that for you. Here is an example route:
const createError = require('http-errors');
app.get('/posts', (req, res, next) => {
// Logic...
if(/* some condition */) {
next(createError(404));
}
});
This will pass the error object created to your error handling middleware, and from there you can choose how to handle it.
To extend this, and to make it work better with asynchronous code, you can wrap your router's callbacks with a function that will make sure exceptions that get thrown are passed over to the error handling middleware, this comes in handy when working with async and await:
// Wrapper function to forward errors from async scopes
const wrap = fn => (...args) => fn(...args).catch(args[2]);
app.get('/posts', wrap(async (req, res) => {
// Logic...
await controller.get('posts'); // Promise rejections will get forwarded to middleware
}));
This also lets you just throw the error object instead of calling next.
I'm trying to add Sequelize transactions to my Express app, but I'm unsuccessful. I'm using async/await across the app, and I've created the namespace using the 'cls-hooked' package as instructed on the docs for Sequelize transactions.
Sequelize.useCLS(require('cls-hooked').createNamespace('db'));
My middleware is pretty simple and looks something like this
module.exports = () => (req, res, next) => sequelize.transaction(async () => next());
and in app.js
app.use(sequelizeTransaction());
app.use('/api', apiRoutes);
I've also tried using the middleware directly on routes, but I get the same result as above
router.post('/', sequelizeTransaction(), async (req, res) => {
await serviceThatDoesTwoDBOperations();
});
The result is that I get the transaction, but only around the first DB operation. Everything after that is ignored, and rollbacks aren't happening on errors. I'm probably doing something obviously wrong, but I can't put my finger on it.
I was having the same issue as you. This is how I made it work. For some reason sequelize was clearing the transaction on the namespace before it was actually completed.
Also make sure that you are using node > 8.5 for the async_hooks.
export const transactionMiddleware = async (req, res, next) => {
namespace.bindEmitter(req);
namespace.bindEmitter(res);
namespace.bind(next);
namespace.run(async () => {
const transaction = await sequelize.transaction();
namespace.set('transaction', transaction);
onFinished(res, (err) => {
if (!err) {
transaction.commit();
} else {
transaction.rollback();
}
});
next();
});
};
I hope that works for you :)
Until now I've defined my get and post handlers with just (req, res) as arguments, with the assumption being that I put these handlers last in the chain of middleware, and make sure that I handle any responses and error handling properly within these handlers... hence it doesn't matter that I don't make any reference to next.
Is this a valid and sensible approach, or is it good practice always to call next() even if (at present) there is nothing coming afterwards? For example, perhaps in the future you might want to do some handling after these routes... or maybe there's a reason I haven't yet come across why it's good practice to always call next().
For example, there is the following simple example in the express routing guide:
app.get('/example/b', function (req, res, next) {
console.log('the response will be sent by the next function ...')
next()
}, function (req, res) {
res.send('Hello from B!')
})
Of course, I appreciate that this is a very simple example to illustrate that handlers can be chained, and is not intended to provide a complete framework for a get handler, but would it be better to define and use next even in the second handler, as follows?
app.get('/example/b', function (req, res, next) {
console.log('the response will be sent by the next function ...')
next()
}, function (req, res, next) {
res.send('Hello from B!')
next()
})
Or is it actually common practice to assume that a handler function that sends a response back to the client should not call next()... i.e. the assumption should be that the chain will end at the handler that actually sends the response?
Or is there no established practice on this point?
I'm even wondering whether it might be common not to send any response in the get handler but to defer that to a dedicated response handler coming after... by which I mean an OK response handler rather than an error response handler (for which it seems to be common practice to defined a final error handler and call next(err)). So, in a non-error situation, you would call next() and in the following middleware you would do your res.status(200).send(req.mydata) where req.mydata is added in your get handler.
No. You should only call next() if you want something else to handle the request. Usually it's like saying that your route may match that request but you want to act like it didn't. For example you may have two handlers for the same route:
app.get('/test', (req, res, next) => {
if (something) {
return next();
}
// handle request one way (1)
});
app.get('/test', (req, res) => {
// handle request other way (2)
});
Always the first matching handler is called, so for the GET /test request the first handler will be called, but it can choose to pass the control to the second handler, as if the first didn't match the request.
Note that if the second handler doesn't intend to pass the request to the next handler, it doesn't even have next in its arguments.
If there was no second handler, then the standard 404 handler would be used if the first one called next().
If you pass an argument to next() then an error handling middleware will be called.
My rule of thumb is to handle the response in the handler if you're going to give a 20x (Success) response code, and in centralized error handling if not. That looks something like this in practice:
// ./routes/things.js
const express = require('express');
const Thing = require('../models/thing');
const Router = express.Router();
// note, the handlers might get pulled out into a controllers file, if they're getting more complex.
router.param('thingId', (req, res, next, id) => {
Thing.findById(id, (e, thing) => {
if (e) return next(e);
// let's say we have defined a NotFoundError that has 'statusCode' property which equals 404
if (!bot) return next(new NotFoundError(`Thing ${id} not found`));
req.thing = thing;
return next();
});
});
router.get('/', (req, res, next) => {
// possibly pull in some sort, limit, and filter stuff
Thing.find({}, (e, things) => {
if (e) return next(e);
res.send(things);
});
});
router.route('/:thingId')
.get((req, res) => {
// if you get here, you've already got a thing from the param fn
return res.send(req.thing);
})
.put((req, res, next) => {
const { name, description } = req.body; // pull whitelist of changes from body
let thing = req.thing;
thing = Object.assign(thing, { name, description }); // copy new stuff into the old thing
thing.save((e) => {
if (e) return next(e);
return res.send(thing); // return updated thing
});
});
Keeping each logical chunk in its own file can reduce repetition
// ./routes/index.js then mounts the subrouters to the main router
const thingsRoute = require('./things');
const express = require('express');
const router = express.Router();
/* .... other routes **/
router.use('/things', thingsRoute);
Error handling is then centralized, and can be mounted either in its own file or right on the app:
// in ./index.js (main app entry point)
const express = require('express');
// this will require by default ./routes/index.js
const routes = require('./routes');
const app = express();
const log = require('./log');// I prefer debug.js to console.log, and ./log.js is my default config file for it
/* ... other app setup stuff */
app.use(routes);
// you can mount several of these, passing next(e) if you don't handle the error and want the next error handler to do so.
app.use((err, req, res, next) => {
// you can tune log verbosity, this is just an example
if (err.statusCode === 404) {
return res.status(404).send(err.message);
}
log.error(err.message);
log.verbose(err.stack); // don't do stack traces unless log levels are set to verbose
return res.status(500).send(err.message);
});