Given the following setup:
const express = require("express");
const app = express();
app.get("/", function(req, res, next) {
// explicitly return an error
return next("my error");
});
// made this middleware for the example,
// solution should typically also work for express default error handling
app.use(function(error, req, res, next) {
if (error) {
res.status(500).send({ error });
throw new Error(error); // <- how to test this?
}
next();
});
app.listen(8080, function() {
console.log("server running on 8080");
}); //the server object listens on port 8080
And for the test:
const request = require("supertest");
const app = require("../../app.js");
const spy = jest.spyOn(global.console, "error").mockImplementation();
it("throws an error", async done => {
const res = await request(app).get("/");
expect(res.status).toBe(500);
expect(res.error.text).toContain("my error");
expect(spy).toHaveBeenCalled(); // nothing...
done();
});
Made a Codesandbox with this example code. Not sure how to run a node test in that though.
async shouldn't be used with done, this results in test timeout in case done() cannot be reached.
First of all, error handler shouldn't re-throw an error, unless it's reusable router instance that is supposed to be augmented with another handler. If it's the last one in a row, it should catch both synchronous and asynchronous errors that can happen inside of it.
The problem is that default error handler is triggered asynchronously so it should be specifically awaited:
it("throws an error", async () => {
const spy = jest.spyOn(global.console, "error");
const res = await request(app).get("/");
expect(res.status).toBe(500);
expect(res.error.text).toContain("my error");
await new Promise(resolve = > setTimeout(resolve));
expect(spy).not.toHaveBeenCalled(); // it really shouldn't
});
A more correct way to approach this is to make sure the error is handled:
it("throws an error", async () => {
const defaultErrorHandler = jest.fn((err, req, res, next) => {});
app.use(defaultErrorHandler);
const res = await request(app).get("/");
expect(res.status).toBe(500);
expect(res.error.text).toContain("my error");
expect(defaultErrorHandler).not.toHaveBeenCalled();
});
Related
I'm using Nodejs Express, the router middleware, in this case, MongoDB and using the POST method via JQuery ajax. Does anyone know why the
router.post doesn't call, but a .get works.
Does anyone know why the router.post
doesn't get triggered when called as a Promise/async.
Async route here doesn't get called on ajax post.
async function asyncMain() {
const uri = "mongodb endpoint";
const client = new MongoClient(uri)
try {
await client.connect().then(console.log('Connected to MongoDB'));
//keeps awaiting cause it never executes.
await pushToDB(client)
//all mongoClient functions work with await here
//Express http Post doesn't work with await here
} catch (e) {
console.error(e);
} finally {
await client.close();
}
}
asyncMain().catch(console.error);
async function pushToDB(client) {
router.post('/', async(req, res, next) => {
const result = await client.db('user').collection('notes').insertOne(req.body);
console.log(`Results ${ result.insertedId }`);
})
}
I tried using this async Middleware implementation with no results.
Such as wrapping the post function's callback in asyncMiddleware to no avail.
const asyncMiddleware = fn =>
(req, res, next) => {
Promise.resolve(fn(req, res, next))
.catch(next);
};
My working sync function for reference.
function main() {
const uri = "mongodb endpoint";
const client = new MongoClient(uri)
client.connect().then(console.log('connected to DB'));
router.post('/', (req, res) => {
const result = client.db('user').collection('notes').insertOne(req.body);
console.log(`Results ${ result.insertedId }`);
})
}
I am trying to have all my error messages in one file, each error is denoted by an error code, then in my functions/services, when there is an error, I call a function that takes the error code as an argument, then returns an object to the client with the error code and the respective error message from the errors.js file.
as an example, a user trying to register with an email that already exists in the database, here is how I try to do it:
// userService.js -- where my register function is
const { errorThrower } = require('../../utils/errorHandlers');
...
static async registerNewUser(body) {
const exists = await User.where({ email: body.email }).fetch();
if(exists) {
errorThrower('400_2');
}
...
}
errorHandlers.js file:
exports.errorThrower = (errCode) => {
throw Object.assign(new Error(errors[errorCode]), { errorCode })
}
exports.errorHandler = (err, req, res, next) => {
if(!err.status && err.errorCode) {
err.status = parseInt(err.errorCode.toString().substring(0, 3), 10);
}
let status, message
if (err.status) {
status = err.status
message = err.message
} else {
status = 500;
message = 'unexpected behavior, Kindly contact our support team!'
}
res.status(status).json({
errorCode: err.errorCode,
message
})
}
errors.js
module.exports = {
'400_1': 'JSON payload is not valid',
'400_2': 'user already registered',
...
}
...
const user = require('./routes/user');
const { errorHandler } = require('../utils/errors');
...
app.use('/user' , user);
app.use(errorHandler);
...
now with this setup, when hitting the register endpoint by postman, I only get the following in the console
UnhandledPromiseRejectionWarning: Error: user already registered
could someone please tell me what am I missing here?
thanks in advance!
You're not catching the error which you throw inside your errorThrower, thus getting the error UnhandledPromiseRejectionWarning. What you need to do is catch the error and pass it on the the next middleware, in order for the errorHandler-middleware to be able to actually handle the error. Something like this:
exports.register = async(req, res) => {
try {
await registerNewUser(req.body);
} catch(err) {
next(err);
}
};
If you don't want to do this for every middleware, you could create a "base"-middleware which handles this:
const middlewareExecutor = async (req, res, next, fn) => {
try {
return await fn.call(fn, req, res, next);
} catch (err) {
next(err);
}
};
Now you can pass your middlewares as an argument and delegate handling the error to the executor:
app.use('/user' , async (req, res, next) => middlewareExecutor(req, res, next, user));
I have an async function as a route handler, and i'd like to have errors handled as some kind of middleware. Here is my working attempt:
router.get(
"/",
asyncMiddleware(
routeProviderMiddleware(
async ({ y }) => ({
body: await db.query({x: y})
})
)
)
)
// This is the middleware that catches any errors from the business logic and calls next to render the error page
const asyncMiddleware = fn =>
(req, res, next) => {
Promise.resolve(fn(req, res, next))
.catch(next)
}
// This is a middleware that provides the route handler with the query and maybe some other services that I don't want the route handler to explicitly access to
const routeProviderMiddleware = routeHandlerFn => async (req, res) => {
const {status = 200, body = {}} = await routeHandlerFn(req.query)
res.status(status).json(body)
}
What I strive to is a way to make the route declaration cleaner - I don't want the 2 middleware wrappers there, ideally i'd like for the business logic function there only, and somehow declare that every route is wrapped in these.
Even combining the two middlewares together would be nice, but I didn't manage.
I use following approach:
Create asyncWrap as helper middleware:
const asyncWrap = fn =>
function asyncUtilWrap (req, res, next, ...args) {
const fnReturn = fn(req, res, next, ...args)
return Promise.resolve(fnReturn).catch(next)
}
module.exports = asyncWrap
All your routes/middlewares/controllers should use this asyncWrap to handle errors:
router.get('/', asyncWrap(async (req, res, next) => {
let result = await db.query({x: y})
res.send(result)
}));
At app.js, the last middleware will receive the errors of all asyncWrap:
// 500 Internal Errors
app.use((err, req, res, next) => {
res.status(err.status || 500)
res.send({
message: err.message,
errors: err.errors,
})
})
Express 5 automatically handles async errors correctly
https://expressjs.com/en/guide/error-handling.html currently says it clearly:
Starting with Express 5, route handlers and middleware that return a Promise will call next(value) automatically when they reject or throw an error. For example:
app.get('/user/:id', async function (req, res, next) {
var user = await getUserById(req.params.id)
res.send(user)
})
If getUserById throws an error or rejects, next will be called with either the thrown error or the rejected value. If no rejected value is provided, next will be called with a default Error object provided by the Express router.
I have shown that in an experiment at: Passing in Async functions to Node.js Express.js router
This means that you will be able to just make the callback async and use await from it directly without any extra wrappers:
router.get("/", async (req, res) =>
const obj = await db.query({x: req.params.id})
// Use obj normally.
)
and errors will be correctly handled automatically.
Express permits a list of middlewares for a route and this approach sometimes works for me better than higher-order functions (they sometimes look like an overengineering).
Example:
app.get('/',
validate,
process,
serveJson)
function validate(req, res, next) {
const query = req.query;
if (isEmpty(query)) {
return res.status(400).end();
}
res.locals.y = query;
next();
}
function process(req, res, next) {
Promise.resolve()
.then(async () => {
res.locals.data = await db.query({x: res.locals.y});
next();
})
.catch((err) =>
res.status(503).end()
);
}
function serveJson(req, res, next) {
res.status(200).json(res.locals.data);
}
What you can do is add an error handlers after your routes. https://expressjs.com/en/guide/error-handling.html
app.use(function (err, req, res, next) {
console.error(err.stack)
res.status(500).send('Something broke!')
})
What I ended up doing is unifying the wrappers like this:
const routeProvider = routeHandlerFn => async (req, res, next) => {
try {
const {status = 200, body = {}} = await routeHandlerFn(req.query)
res.status(status).json(body)
} catch(error) {
next(error)
}
}
This wrapper is all any route would need. It catches unexpected errors and provides the route handler with the needed params.
I have an async middleware in express, because I want to use await inside it, to clean up my code.
const express = require('express');
const app = express();
app.use(async(req, res, next) => {
await authenticate(req);
next();
});
app.get('/route', async(req, res) => {
const result = await request('http://example.com');
res.end(result);
});
app.use((err, req, res, next) => {
console.error(err);
res
.status(500)
.end('error');
})
app.listen(8080);
The problem is that when it rejects, it doesn't go to my error middleware, but if I remove the async keyword and throw inside a middleware it does.
app.get('/route', (req, res, next) => {
throw new Error('Error');
res.end(result);
});
So I'm getting UnhandledPromiseRejectionWarning instead of entering my error handling middleware, how can I let the error bubble up, and express handle it?
The problem is that when it rejects, it doesn't go to my error
middleware, but if I remove the async keyword and throw inside a
middleware it does.
express doesn't support promises currently, support may come in the future release of express#5.x.x
So when you pass a middleware function, express will call it inside a try/catch block.
Layer.prototype.handle_request = function handle(req, res, next) {
var fn = this.handle;
if (fn.length > 3) {
// not a standard request handler
return next();
}
try {
fn(req, res, next);
} catch (err) {
next(err);
}
};
The problem is that try/catch won't catch a Promise rejection outside of an async function and since express does not add a .catch handler to the Promise returned by your middleware, you get an UnhandledPromiseRejectionWarning.
The easy way, is to add try/catch inside your middleware, and call next(err).
app.get('/route', async(req, res, next) => {
try {
const result = await request('http://example.com');
res.end(result);
} catch(err) {
next(err);
}
});
But if you have a lot of async middlewares, it may be a little repetitive.
Since I like my middlewares as clean as possible, and I usually let the errors bubble up, I use a wrapper around async middlewares, that will call next(err) if the promise is rejected, reaching the express error handler and avoiding UnhandledPromiseRejectionWarning
const asyncHandler = fn => (req, res, next) => {
return Promise
.resolve(fn(req, res, next))
.catch(next);
};
module.exports = asyncHandler;
Now you can call it like this:
app.use(asyncHandler(async(req, res, next) => {
await authenticate(req);
next();
}));
app.get('/async', asyncHandler(async(req, res) => {
const result = await request('http://example.com');
res.end(result);
}));
// Any rejection will go to the error handler
There are also some packages that can be used
async-middleware
express-async-handler
Well, I found this - https://github.com/davidbanham/express-async-errors/, then require the script and you are good to go
const express = require('express');
require('express-async-errors');
Answer with asyncHandler is good and usefull, but it is still not comfortable to write this wrapper in every route. I propose to improve it:
const asyncHandler = fn => (req, res, next) => {
return Promise
.resolve(fn(req, res, next))
.catch(next)
}
const methods = [
'get',
'post',
'delete' // & etc.
]
function toAsyncRouter(router) {
for (let key in router) {
if (methods.includes(key)) {
let method = router[key]
router[key] = (path, ...callbacks) => method.call(router, path, ...callbacks.map(cb => asyncHandler(cb)))
}
}
return router
}
and now we can do that way:
const router = toAsyncRouter(express().Router())
router.get('/', someAsyncController)
and so one.
Minute ago added a npm module async-express-decorator.
You need to use try-catch and in catch section just pass the error in next() parameter Like this -
async create(req, res, next) {
try {
const userProp = req.body;
const user = new User(userProp)
const response = await user.save()
const token = await user.createJWSToken()
res.send({response, token})
} catch (err){
next(err)
}
}
And obviously put this express middleware on your index.js file.
app.use((err, req, res, next) => {
res.status(422).send({ error: err.message });
});
Express 5 now handle async promises:
https://expressjs.com/en/guide/error-handling.html
Starting with Express 5, route handlers and middleware that return a
Promise will call next(value) automatically when they reject or throw
an error. For example
You need to callbackify your async handler. If you know the concept of promisify, this is the opposite. Callbackify is built-in in Node.
import util from 'util'
app.use(util.callbackify(async (req, res) => {
await authenticate(req);
}));
What this does is that it returns a function with a third argument which would be the next function and calls it after the promise has been resolved. If the promise is rejected, the next function will be called with the error as an argument.
Suppose I have a route like this:
app.get('/broken', (req, res) => {
throw new Error('Broken!');
});
This will never send a response to clients.
However, I can add a middleware for all errors:
const errorMiddleware = (error, req, res, next) => {
if (error) {
console.error(error);
return res.status(500)
.json({
message: 'Internal server error',
});
}
next(error);
};
But this will not work for async routes, because they do not throw directly.
For example, this will not work:
app.get('/broken', async (req, res) => {
throw new Error('Broken!');
});
So I can create a wrapper like this:
const asyncRoute = f => (req, res, next) => {
return Promise.resolve(f(req, res, next)).catch(next);
};
app.get('/broken', asyncRoute(async (req, res) => {
throw new Error('Broken!');
}));
But this is a real pain, because now I have to call this function for every route!
What is a better way of handling this?
The answer to Is there a way to wrap an await/async try/catch block to every function? is just what I describe above
The answer to how to use Promise with express in node.js? does not use await
Fundamentally, you don't want to directly pass an async function into Express's app.get, because app.get doesn't handle the promise the function returns. So you'll need to wrap those async handlers (as you're doing).
You can avoid having to do it every time by giving yourself a utility method at the top of the module:
const appGet = handler => app.get(asyncRoute(handler));
then use it instead of app.get:
appGet('/broken', async (req, res) => {
throw new Error('Broken!');
});
At some point (probably not right now), you might want to look at Koa.