Here the three frameworks converge on the same underlying concept — some dedicated hook that stays quiet until a request fails — while disagreeing entirely on the plumbing that routes control there. The error Counter actually trips over most is Ridgeline Pay's refund API timing out partway through an approval:
// ASP.NET Core — a plain try/catch around await next(), or the built-in helper:
app.UseExceptionHandler("/error");
// Express — a synchronous throw is caught automatically; a rejected
// promise from an async handler is NOT, on Express 4, without help
app.post("/returns/:returnId/approve", authenticate, requireManager, async (req, res, next) => {
try {
const result = await approveReturn(req.params.returnId, req.body);
res.json(result);
} catch (err) {
next(err); // required on Express 4 — do not assume this happens on its own
}
});
# Django — process_exception, or a plain try/except around get_response
class RefundErrorMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
return self.get_response(request)
def process_exception(self, request, exception):
if isinstance(exception, RidgelinePayTimeout):
log.warning("Ridgeline Pay timed out mid-approval: %s", request.path)
return None # None means "keep propagating" — this middleware isn't swallowing it
The Express snippet deserves a second look, past the arity trick. On Express 4, catching a plain throw happens for free, but a promise that rejects somewhere inside an async route handler gets no such favor — nothing routes it to an error handler unless the code explicitly says so. Leave that .catch out and one of two things happens depending on what else the process has wired up: either the rejection goes entirely unhandled and takes the process down with it, or the one request in flight just stalls, neither of which is the tidy 500 Counter's on-call rotation actually wants to page on. (Express 5 closes this gap on its own — a rejected promise from a handler is forwarded automatically there, the same as a synchronous throw always was — but Counter's Express prototype, like most production Express code as of this writing, is still running 4.)