Express.js / Editorial solution guide
Diagnose pending requests, double responses and async errors
Locate the missing or duplicated owner of completion before increasing a timeout or adding more catch blocks.
Read-only knowledge base · No public comments
Recognize the symptoms
A request never finishes, an error about headers already being sent appears, or a promise fails without the intended public error response. These are different lifecycle faults. Capture a safe route identifier, method, response commitment state and the phase where work stopped; avoid logging the private body or credential.
Understand the likely causes
- Common causes are a branch that neither responds nor calls next, a response followed by continued JavaScript, repeated next calls, detached promises, callback errors outside the returned promise, or error middleware registered before the failing route. A source timeout may instead mean unfinished external work, so distinguish the local control path from the dependency.
Preserve the branch and commitment evidence
Start with one synthetic reproduction and the exact middleware registration order. Note whether the handler began, whether it called a source, whether headers were sent and whether the response finished or closed early. headersSent does not prove complete client delivery. A pending request before source access suggests a local branch problem, while a pending awaited lookup needs source-stage evidence. Keep observations from the same request correlation; combining a successful handler trace with another request’s error can invent a false double-send diagnosis. Do not solve uncertainty by logging every body or secret.
Write down one owner for every branch
The JSON record is a synthetic branch review, not a captured production trace. For each actual branch, require a final response, normal transfer or error transfer with a clearly owned asynchronous continuation. After a final response, return when later statements must not execute. Do not call next as routine cleanup after sending. If two callbacks can race, coordinate completion once rather than allowing both to send. A handler that starts detached work needs a separate background-task protocol; it cannot both report durable success and ignore that task’s eventual failure.
{"branch":"synthetic-source-failure","sourcePromise":"returned-or-awaited","completionOwner":"one-error-path","afterFinalResponse":"return","headersAlreadySent":"delegate-and-clean-up","privatePayloadLogged":false}
The record names invariants only. It does not reproduce Express dispatch or claim an observed error.
Connect asynchronous failure once
For Express 5, return or await the promise that owns request work so rejection reaches the automatic error path. A timer or event callback remains separate unless deliberately wrapped or forwarded. Remove wrappers that both call next(error) and rethrow into the returned promise. Place a four-argument error handler after the relevant routes. If headers are committed, delegate under the documented error path rather than writing another JSON body. Fix a local ownership fault narrowly; extending a timeout cannot repair a branch that has no completion path at all.
Verify output and effects together
Use deterministic service fakes to exercise success, denied input, source rejection, callback failure and late failure after commitment as separate cases. Assert one response or one forwarding action and no protected source call after rejection. For a write timeout, reconcile authoritative state because the effect may already have committed. A local mapper or branch model cannot prove HTTP connection behavior. This guide performs no server experiment; retain actual engine and transport checks for an authorized isolated integration using the project’s approved installed tools.
Verify the fix
The repaired fixture must finish or forward exactly once, preserve a safe public result and release owned resources. Include a branch that previously hung and a branch that previously continued after sending. Keep before/after stage evidence and verify no unexpected write under rejected input.
Keep it from recurring
- Keep handlers narrow, use explicit returns, give async work one owner and test rejected promises as well as successes. Maintain separate ordinary unmatched handling and final error translation. Record the Express major version so Express 5 promise behavior is not assumed for another runtime stack.