I've enabled CLS transaction control in Sequelize with 'cls-hooked' in a test project, just to get the gist of it. So it worked just fine in the command line as well as with Express, transactions are being injected and managed automatically, and it rolls back on any error as it should.
Bellow you can see how simple the code became in Express, I just wrap the call to my service layer inside a single sequelize transaction function. So far so good...
router.post('/', (req, res) => {
sequelize.transaction(async () => {
res.status(201).json(await
CountryService().add(req.body));
});
});
router.post('/', async (req, res) => {
sequelize.transaction(async () => {
res.status(201).json(await
CountryService().add(req.body));
});
});
Now the problem became error handling. I don't want to have to ".catch(error => { 'do error related stuff here'; })" for every endpoint (not to mention Sequelize-related errors that can 'leak' from Express, for instance, 'Connection Refused.' if I can't reach the DBMS).
I want to have a "catch-all middleware" taking care of it all.
In my other projects, where CLS managed transactions are not enabled(and thus, I'm committing and rolling back myself, as well as passing a transactions instance to every Sequelize call), I can have my error middleware passed to Express and it deals well with any contingency, throwing in even 'express-async-errors' for good measure.
Now, with or without 'express-async-errors', with or without async-ing the endpoint (as you can see above, both a 'sync' and 'async' version), I get those dreaded (node:2528) UnhandledPromiseRejectionWarning: Error.
With the following:
UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch().
To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
(node:2528) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
Both my service function and my endpoint are in the stack trace, as you can see bellow:
at async Object.add (C:\Users\...\VSCode\sqlize-test\services\country.js:9:16)
at async C:\Users\...\VSCode\sqlize-test\routes\country.js:10:30
So we can say that it's understood the error happens inside my service layer, than jumps to my controller/rest/api layer.
The question is, why can't my "catch-all middleware" catch it? It is the same middleware, the same configuration I'm using in other projects without nested transactions with CLS, so I'm a little lost here.
In order to provoke an error, I sent via POST the same country twice, so it violated an unique key, but it will happen with sequelize validation errors as well as any other.
Thanks for your attention.
CodePudding user response:
Assuming that catch all middleware
is something like:
app.use(function (err, req, res, next) {
logger.error(err)
res.status(500).send(`<pre>${err.stack}</pre>`)
})
you just need to get exceptions from async DB calls:
router.post('/', async (req, res) => {
await sequelize.transaction(async () => {
res.status(201).json(await
CountryService().add(req.body));
});
});