Built‑in interceptor vs manual composition for request cancellation and timeout in NestJS
0 reputation · 23 Jul 2022, 10:45 UTC
0 reputation · 23 Jul 2022, 10:45 UTC
Goal: provide a consistent way to abort server‑side work when a client disconnects or when a processing deadline is exceeded, while keeping boilerplate low and ensuring the same behavior whether the app runs on Express or Fastify.
Constraints: the ‘close’ event is emitted immediately by Express but only after the response is flushed by Fastify; NestJS versions before 9.0 require accessing the raw request object (e.g., req['req']) to attach the listener; cancellation must be propagated to long‑running tasks such as database queries, and a timeout operator may abort legitimate slow requests unless the operation itself checks for interruption. It is also unclear whether the combined logic should live in a globally applied interceptor, a per‑module provider, or be left to developers to compose manually.
Should NestJS ship a built‑in interceptor that automatically combines request close detection with a configurable timeout operator and integrates with AsyncLocalStorage for request‑scoped services? What are the trade‑offs of bundling these concerns versus keeping them separate? How should the interceptor handle the AsyncLocalStorage context when cancelling a request?
34275 reputation · 23 Jul 2022, 15:21 UTC
The primary hurdle is how underlying platforms handle client disconnects. Express emits the close event immediately when a connection drops, whereas Fastify may wait until the response is flushed. Relying on manual listeners in controllers creates inconsistent behavior. Furthermore, an RxJS timeout operator only terminates the stream; it does not natively stop long-running asynchronous tasks like database queries unless those tasks are explicitly linked to a signal AbortSignal.
By using a global interceptor, you can manage the lifecycle of the request in one place. Using AsyncLocalStorage allows you to propagate the cancellation signal to deep-nested services without modifying their function signatures.
AbortController per request.AbortController for every request.close event on the raw request (handling the adapter difference) to call abort().timeout() operator to the observable to handle processing deadlines.AbortSignal.// Inside the Interceptor
const controller = new AbortController();
this.als.run(controller.signal, () => {
return next(handle).pipe(
timeout(requestTimeout),
catchError(err => {
if (err instanceof TimeoutError) controller.abort();
throw err;
})
);
});
Note: Ensure your chosen database driver or external API client supports AbortSignal; if not, the cancellation will stop the Nest response but the server-side work will continue.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.