openvibers/openvibe.shared express-async.js

express-async.js

main 2.5 KB · 52 lines · javascript

1'use strict';
2/**
3 * openvibe-shared/express-async — route an async Express 4 handler's rejection to next(err).
4 *
5 * Express 4 calls a handler and ignores what it returns, so a promise an `async (req, res) => …` handler rejects
6 * (a database error, a thrown problem) becomes an unhandled rejection, and Node 22 ends the process on one. Services
7 * moved to PostgreSQL (ADR-035) made most handlers async, so every OpenVibe service needs this. It patches the Layer
8 * class of the app's own express copy, once: a handler's rejection goes to next(err), where the service's error
9 * handler answers (a 500 problem), exactly as a synchronous throw always did. Error handlers (four arguments) get the
10 * same treatment. Express 5 handles promises itself: there it does nothing.
11 *
12 * require('openvibe-shared/express-async').routeAsyncErrors(app); // instrument() (openvibe-shared/metrics) calls it
13 */
14const PATCHED = Symbol.for('openvibe.shared.expressAsync');
15
16function layerClassOf(app) {
17 if (!app || typeof app.use !== 'function') return null;
18 if (!app._router && typeof app.lazyrouter === 'function') app.lazyrouter(); // Express 4 only
19 const router = app._router;
20 if (!router || !Array.isArray(router.stack) || !router.stack.length) return null;
21 const Layer = router.stack[0].constructor;
22 return Layer && Layer.prototype && typeof Layer.prototype.handle_request === 'function' ? Layer : null;
23}
24
25function settle(out, next, fallback) {
26 if (out && typeof out.then === 'function') out.then(undefined, (err) => next(err || fallback || new Error('async handler rejected')));
27}
28
29/** → true when it patched, false when there was nothing to patch (Express 5, or patched already). */
30function routeAsyncErrors(app) {
31 const Layer = layerClassOf(app);
32 if (!Layer || Layer.prototype[PATCHED]) return false;
33 Layer.prototype.handle_request = function handle(req, res, next) {
34 const fn = this.handle;
35 if (fn.length > 3) return next(); // an error handler is not a request handler (as Express does)
36 let out;
37 try { out = fn(req, res, next); } catch (err) { return next(err); }
38 return settle(out, next);
39 };
40 Layer.prototype.handle_error = function handleError(error, req, res, next) {
41 const fn = this.handle;
42 if (fn.length !== 4) return next(error);
43 let out;
44 try { out = fn(error, req, res, next); } catch (err) { return next(err); }
45 return settle(out, next, error);
46 };
47 Layer.prototype[PATCHED] = true;
48 return true;
49}
50
51module.exports = { routeAsyncErrors };
52