openvibers/openvibe.shared express-async.js
express-async.js
| 1 | ; |
| 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 | */ |
| 14 | const PATCHED = Symbol.for('openvibe.shared.expressAsync'); |
| 15 | |
| 16 | function 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 | |
| 25 | function 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). */ |
| 30 | function 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 | |
| 51 | module.exports = { routeAsyncErrors }; |
| 52 |