Express.js
Asincronía en handlers
Explica cómo Express.js 5 maneja handlers async, Promises rechazadas, errores después de enviar headers y operaciones concurrentes dentro de una request.
- Última actualización
- Actualizada
- Nivel
- Aplicación
Express.js
Explica cómo Express.js 5 maneja handlers async, Promises rechazadas, errores después de enviar headers y operaciones concurrentes dentro de una request.
async expresa espera y composición de Promises. No crea paralelismo, no elimina carreras y no vuelve durable el trabajo iniciado dentro de una request.
Un handler asíncrono puede esperar base de datos, red o filesystem sin bloquear el event loop mientras la operación está pendiente:
router.get('/:id', async (request, response) => {
const order = await orders.findById(request.params.id);
response.status(200).json({ data: order });
});En Express 5, si la Promise retornada por el handler se rechaza, Express propaga el error al error middleware.
handler inicia operación
↓
Promise pendiente
↓ event loop atiende otro trabajo
Promise resuelve o rechaza
↓
handler continúa o error pipelineEsto es concurrencia asíncrona, no ejecución paralela del JavaScript del handler.
Express puede observar:
return doSomethingAsync();o la Promise implícita de una función async.
No puede observar automáticamente:
router.post('/', (_request, response) => {
doSomethingAsync(); // Promise ignorada
response.sendStatus(202);
});Si esa Promise falla, queda fuera del lifecycle normal.
const customer = await customers.findById(customerId);
const orders = await ordersRepo.findByCustomer(customer.id);La segunda operación depende de la primera, así que la secuencia es correcta.
const [profile, preferences] = await Promise.all([
profiles.findByUser(userId),
preferences.findByUser(userId),
]);Úsalo cuando las operaciones son independientes. Promise.all rechaza al primer fallo observado, pero las demás operaciones pueden continuar porque las Promises no se cancelan automáticamente.
Útil cuando necesitas observar todos los resultados:
const results = await Promise.allSettled(tasks);No lo uses para ocultar fallos críticos. Decide qué resultados parciales son aceptables.
Dos handlers concurrentes pueden leer el mismo estado:
request A lee stock 1
request B lee stock 1
A descuenta
B descuentaawait no serializa requests. La base de datos, locks, updates condicionales o constraints deben proteger invariantes.
Un loop pesado o criptografía síncrona bloquea el event loop aunque el handler sea async:
router.get('/report', async (_request, response) => {
const report = expensiveSynchronousCalculation();
response.json(report);
});Para trabajo grande considera worker threads, procesos separados o jobs.
readFile(path, (error, data) => {
if (error) {
next(error);
return;
}
response.send(data);
});Una excepción dentro de un callback posterior no está necesariamente conectada a la Promise del handler. Convierte APIs a Promise o propaga explícitamente.
Enviar email, cobrar o publicar eventos después de responder sin persistencia pierde trabajo si el proceso cae.
Opciones:
void sendEmail() solo silencia TypeScript; no crea confiabilidad.
Un timeout del cliente puede ocurrir después del commit. Si el cliente reintenta un POST sin idempotencia, puede duplicar el efecto.
El diseño debe separar:
¿terminó la request?
≠
¿se confirmó la operación?router.post('/', async (_request, response) => {
const order = await createOrder.execute({
actor: response.locals.actor,
input: response.locals.validatedBody,
});
response.status(201).json({ data: toOrderResponse(order) });
});El caso de uso confirma su transacción antes de devolver. El handler no envía 201 anticipadamente.
El error middleware no puede reemplazar la response. Registra y evita iniciar trabajo tardío dentro del handler.
Las demás pueden seguir consumiendo recursos. Usa AbortSignal cuando las APIs lo soporten.
El handler puede continuar. Propaga cancelación si el trabajo es costoso.
Puede serializar operaciones innecesariamente. También puede ser correcto para respetar rate limits o dependencias.
Pueden multiplicar efectos no idempotentes.
Promise.all entre operaciones dependientes.await evita concurrencia entre requests.Incluye:
Esperar efectos ofrece resultado fuerte pero aumenta latencia. Desacoplar mediante cola mejora respuesta y resiliencia, pero introduce consistencia eventual, retries y operación adicional.
async no significa paralelo ni serializado globalmente.Promise.all no cancela automáticamente.Promise.all rechaza?await no protege stock concurrente?Cancelación, abort y timeouts añade límites de tiempo y propagación de abandono a cada dependencia.