Express.js
Cancelación, abort y timeouts
Explica cómo cancelar trabajo cuando el cliente desconecta, propagar AbortSignal y definir timeouts coherentes para base de datos, servicios y responses.
- Última actualización
- Actualizada
- Nivel
- Profundización
Express.js
Explica cómo cancelar trabajo cuando el cliente desconecta, propagar AbortSignal y definir timeouts coherentes para base de datos, servicios y responses.
Un timeout limita cuánto espera una capa. No garantiza que el trabajo cancelado haya dejado de ejecutarse ni que una operación remota no haya confirmado.
Una request atraviesa varios límites de tiempo:
cliente
→ CDN / load balancer
→ reverse proxy
→ servidor Node.js
→ caso de uso
→ PostgreSQL / proveedor externoCada capa puede abandonar antes que la siguiente. Diseñar timeouts exige coordinar deadlines y propagar cancelación.
Sin límites, una dependencia lenta retiene:
Con límites mal coordinados, el proxy puede cortar mientras la aplicación sigue trabajando o el servidor puede responder 504 antes de registrar la causa real.
timeout
→ dejamos de esperar
cancelación
→ solicitamos que el trabajo se detengaPromise.race crea un timeout de espera, pero la Promise perdedora continúa:
await Promise.race([operation(), timeout(1000)]);Para detener, la operación debe aceptar una señal o mecanismo propio.
function requestAbortSignal(request: Request): AbortSignal {
const controller = new AbortController();
const abort = () => controller.abort();
request.once('aborted', abort);
request.once('close', abort);
return controller.signal;
}Distingue close normal de cierre temprano si la señal se usa en logging; el evento puede ocurrir en varios momentos según objetos y versión.
Node ofrece AbortSignal.timeout:
const signal = AbortSignal.timeout(2_000);
const response = await fetch(url, { signal });Para combinar deadline y cancelación del cliente, utiliza AbortSignal.any en versiones de Node que lo soporten:
const signal = AbortSignal.any([
requestSignal,
AbortSignal.timeout(2_000),
]);Verifica soporte de la versión de Node desplegada.
Si el proxy corta a 30 segundos, no configures una llamada externa a 30 segundos y luego una query adicional. Reserva tiempo para:
Ejemplo:
proxy: 30 s
request server: 25 s
provider: 5 s
PostgreSQL statement: 3 sLos números dependen del workload, no son defaults universales.
Un timeout de aplicación no siempre cancela la query. Configura:
statement_timeoutNo dejes una transacción abierta después de abandonar la response.
fetch soporta AbortSignal, pero el proveedor pudo recibir y procesar la request antes de la cancelación. Para mutations externas necesitas idempotency keys o consulta de estado.
Un retry consume más tiempo y carga. Debe cumplir:
const delay = base * 2 ** attempt + randomJitter;No reintentes 400, 401 o conflictos que requieren cambiar entrada.
Cuando una dependencia falla repetidamente, un circuit breaker puede rechazar rápido y permitir recuperación. Introduce estado, métricas y tuning; no es necesario para cada llamada.
router.get('/:id', async (request, response) => {
const signal = AbortSignal.any([
requestAbortSignal(request),
AbortSignal.timeout(3_000),
]);
const order = await getOrder.execute({
actor: response.locals.actor,
orderId: response.locals.orderId,
signal,
});
if (signal.aborted || response.destroyed) return;
response.status(200).json({ data: toOrderResponse(order) });
});El caso de uso pasa la señal a dependencias compatibles. No toda función de dominio necesita recibirla; solo operaciones cancelables.
Ante éxito, error o abort:
Usa finally alrededor de recursos adquiridos:
const client = await pool.connect();
try {
// work
} finally {
client.release();
}El cliente recibe error, pero la operación existe. Idempotencia y endpoint de consulta son necesarios.
El cleanup no debe depender de una señal ya abortada si necesita completar rollback.
Cancelar puede añadir más complejidad que terminar. No propagues señales por dogma.
Un timeout global puede cortar una descarga legítima. Usa timeouts de inactividad y límites apropiados.
Una request puede cancelar la espera, pero un job persistido debe continuar según su contrato.
Promise.race asumido como cancelación.Registra:
Distingue CLIENT_ABORTED, UPSTREAM_TIMEOUT, DB_TIMEOUT y SERVER_DEADLINE.
Timeouts agresivos protegen recursos pero aumentan fallos visibles. Retries mejoran éxito transitorio pero amplifican tráfico. Cancelación reduce trabajo inútil, aunque no todas las dependencias pueden detenerse.
Promise.race no detiene la operación perdedora?Cookies seguras estudia cómo el navegador persiste y envía estado automáticamente.