Basically I want to instead of this...
app.get(routes.test, function(req, res, next){
actualRouteHandler(req, res, next) // Always returns a promise or throws.
.catch(function(err) {
next(err);
});
});
Have this:
app.get(routes.test, catchWrap(actualRouteHandler));
Or something similar, I've tried messing around with fn.apply and things but I can't find a way to pass actualRouteHandler the correct parameters (req,res,next) and still have the function. Do I need to return a function or something similar?
Edit: I think there may be libraries that do this but we have no access to the actual express application in this bit of code.
In your specific case, catchWrap
would look like this:
function catchWrap(originalFunction) {
return function(req, res, next) {
try {
return originalFunction.call(this, req, res, next);
} catch (e) {
next(e);
}
};
}
That returns a new function that, when called, will call the original with your catch wrapper around it. The key parts are that it creates and returns a function (return function(req, res, next) { ... };
) and this line:
return originalFunction.call(this, req, res, next);
Function#call
calls the given function saying what to use as this
during the call (in the above we pass on the this
we recieved) and the arguments to use in the call.
You'd use it as you showed:
app.get(routes.test, catchWrap(actualRouteHandler));
Or if you prefer to define the actual handler as an anonymous function:
app.get(routes.test, catchWrap(function(req, res, next) {
// ...handler code here...
}));
That catchWrap
is specific to your situation, because you want to call next(e)
with the exception if thrown. The generic form of "wrap this function in another function" is like this:
function catchWrap(originalFunction) {
return function() {
try {
// You can do stuff here before calling the original...
// Now we call the original:
var retVal = originalFunction.apply(this, arguments);
// You can do stuff here after calling the original...
// And we're done
return retVal;
} catch (e) {
// you can do something here if you like, then:
throw e; // Or, of course, handle it
}
};
}
arguments
is a pseudo-array provided by JavaScript that includes all of the arguments that the current function was called with. Function#apply
is just like Function#call
, except you give the arguments to use as an array (or pseudo-array) rather than discretely.
Taking it a step further: You could create a function you passed a router to that returned an updated router that auto-wrapped the handlers when you called get
, post
, etc. Something along these lines (untested):
const routeCalls = ["all", "get", "post", "put", "delete", /*...*/];
function patchWithCatcher(router) {
for (const call of routeCalls) {
const original = router[call];
// This seemingly-pointless object is to ensure that the new function
// has a *name*; I don't know whether Express relies on the name of
// the function you call (some libs with similar APIs do).
const rep = {
[call]: function (path, ...callbacks) {
return router[call](path, ...callbacks.map(catchWrap));
},
};
router[call] = rep[call];
}
return router; // Just for convenience
}
Using it:
const app = patchWithCatcher(express());
app.get(/*...*/);
app.post(/*...*/);
// ...
or
const router = patchWithCatcher(express.Router(/*...options...*/));
router.get(/*...*/);
router.post(/*...*/);
// ...
That monkey-patches the router you pass in. I considered using the passed-in router as the prototype of a new object in order to avoid modifying the router object passed in, but you'd have to test that really, really thoroughly, there are lots of edge cases around this
during calls. Another viable alternative would be to use a Proxy
, though at a runtime cost.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With