Replies: 5 comments 2 replies
-
|
If your code looks like the examples that you have provided, then the exceptions from Are you sure that the unhandled exception is thrown in a route handler and not somewhere else? Answers
Like you demonstrated - if a handler returns a promise, Express appends a const express = require("express");
const app = express();
async function asyncErrorThrower() {
throw new Error("An async error");
}
app.get("/async", async (req, res) => {
await asyncErrorThrower();
});
const server = app.listen(0, "127.0.0.1", async () => {
const { address, port } = server.address();
const r = await fetch(`http://${address}:${port}/async`);
const t = await r.text();
console.log(path, r.status, t);
server.close();
});
Yes, see 1.
There is no need to. Express 5 catches both synchronous and async errors in route handlers.
It is unnecessary, so probably there is none - Express can handle async errors (see 1., 2. and 3.)
I'm not sure if I understand this question. Do you want to shut down the server on error for some reason? |
Beta Was this translation helpful? Give feedback.
-
|
@krzysdz Thanks for the detailed explanation! But I have a follow-up that complicates things: app.get("/complex", async (req, res) => {
const stream = new PassThrough();
// This error is NOT caught by Express error handler
setImmediate(async () => {
const data = await db.query("SELECT * FROM users");
stream.write(JSON.stringify(data));
stream.end();
});
stream.pipe(res);
});The scenario above: error thrown inside
Is there a recommended pattern for these "detached async" scenarios in Express 5, or do we still need Also, what about this edge case: app.use(async (req, res, next) => {
req.data = await fetchData(); // If this throws AFTER next() is called...
next();
});If |
Beta Was this translation helpful? Give feedback.
-
|
The important boundary is whether the promise is returned to Express. Express 5 handles the promise returned by the route/middleware function. It cannot see work that you start and then detach. So this is caught: app.get("/users", async (req, res) => {
const users = await db.query("SELECT * FROM users");
res.json(users);
});But this is not caught by Express, because the handler has already returned before the app.get("/complex", async (req, res) => {
setImmediate(async () => {
await db.query("SELECT * FROM users");
});
res.end();
});For those detached cases you still need to handle the error at the place where you detach the work: setImmediate(() => {
db.query("SELECT * FROM users").catch((err) => {
// log, metric, queue retry, etc.
});
});If the async work is still needed to produce the response, do not detach it. Return/await it inside the handler so Express owns the rejection: app.get("/complex", async (req, res, next) => {
try {
const data = await db.query("SELECT * FROM users");
res.json(data);
} catch (err) {
next(err);
}
});You only need the explicit For streams, use the stream error channel. A stream can fail after the route promise has already resolved, so route-level async error handling is not the right mechanism: stream.on("error", (err) => {
if (!res.headersSent) next(err);
else res.destroy(err);
});
stream.pipe(res);For your middleware example as written: app.use(async (req, res, next) => {
req.data = await fetchData();
next();
});If
|
Beta Was this translation helpful? Give feedback.
-
|
@lizuju Perfect, this clears up a lot! So the key insight is: Express 5 only catches the promise returned by the handler itself — anything detached (setImmediate, EventEmitter, fire-and-forget) is outside Express's reach. For streams, your pattern makes sense: stream.on('error', (err) => {
if (!res.headersSent) next(err);
else res.destroy(err);
});One follow-up: what about async middleware that mutates request state? app.use(async (req, res, next) => {
try {
req.user = await getUserFromDB(req.userId);
next();
} catch (err) {
next(err); // This works
}
});
// But what if I do this?
app.use(async (req, res, next) => {
req.auditLog = logToAuditService(req); // Fire-and-forget
next(); // Don't await it
});If Also, for the |
Beta Was this translation helpful? Give feedback.
-
No — Express 5 wraps only the promise returned by the handler. Once next() is called and the handler returns, Express has released the request. That rejection becomes an unhandledRejection. Recommended pattern: app.use(async (req, res, next) => {
req.auditLog = logToAuditService(req).catch(err => {
console.error('Audit log failed:', err)
})
next()
})Always attach .catch() to any promise you detach. The only things hitting process.on('unhandledRejection') are promises you forgot to handle — use it as a diagnostic signal. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Problem
I am migrating from Express 4 to Express 5. In Express 4, I used try/catch in every route handler. Express 5 supports async handlers natively, but unhandled rejections crash the server.
Current approach (Express 4)
Express 5 approach
Problems
What I tried
Questions
Environment
Any insights on error handling patterns in Express 5 would be appreciated.
Beta Was this translation helpful? Give feedback.
All reactions