Un fetch externo puede quedarse esperando a una dependencia lenta mientras el resto de la aplicación espera la Promise. Si tu servicio tiene un plazo de respuesta, coloca ese plazo en la señal que pasas a fetch. No escondas un temporizador en una Promise paralela que solo abandona el resultado.
En Node.js, AbortSignal.timeout(milliseconds) crea una señal que se aborta después del retraso indicado. Pásala como signal y conserva el mismo límite mientras consumes el cuerpo de la respuesta. Si quien llama también puede cancelar, combina la señal manual con el plazo mediante AbortSignal.any().
Este artículo usa TypeScript y APIs nativas. Comprobé la lógica equivalente con un servidor HTTP local que retrasa las cabeceras y el cuerpo, sin medir una aplicación en producción. La documentación actual de Node.js sobre objetos globales, consultada el 28/09/2026, registra los métodos y sus versiones de incorporación. La referencia de MDN para AbortSignal.timeout(), consultada el mismo día, documenta TimeoutError, AbortSignal.any() y el hecho de que el timeout no tiene un método propio para cancelarse.

Respuesta corta
- Pasa
AbortSignal.timeout(plazo)en la opciónsignaldefetch.- Combina el plazo con la cancelación del emisor mediante
AbortSignal.any().- Distingue timeout, cancelación manual, fallo de red y estado HTTP.
- No trates un abort del cliente como rollback de un trabajo que ya llegó al servidor.
¿fetch tiene su propio timeout en Node.js?
No confundas el tiempo que tu aplicación acepta esperar con un límite interno del transporte. La forma explícita de controlar la llamada es pasar un AbortSignal. Node.js documenta AbortSignal.timeout(delay) como el método que crea una señal abortada después del retraso indicado.
Para una llamada sencilla, el código es corto:
async function loadProfile(url: string): Promise<unknown> {
const response = await fetch(url, {
signal: AbortSignal.timeout(5_000),
});
if (!response.ok) {
throw new Error(`El servicio respondió HTTP ${response.status}`);
}
return response.json();
}
El plazo permanece asociado a la operación de fetch. response.json() también queda dentro de esa operación asíncrona. Si la respuesta tarda más que el límite, la Promise se rechaza y quien llama debe decidir cómo registrar, responder o reintentar.
El valor 5_000 es solo un ejemplo. El plazo correcto depende del contrato de la ruta y de su dependencia. No existe un número universal que vuelva saludable una llamada lenta.
¿Cómo combinar un timeout con una cancelación manual?
Usa dos señales cuando hay dos motivos legítimos para detener la llamada: el plazo de la operación y una decisión del emisor. AbortSignal.any() devuelve una señal que se aborta cuando se aborta cualquiera de las señales de la lista y conserva la razón de la señal que ganó, según la documentación de Node.js.
type RequestOptions = {
signal?: AbortSignal;
timeoutMs: number;
};
async function loadJson<T>(
url: string,
options: RequestOptions,
): Promise<T> {
const deadline = AbortSignal.timeout(options.timeoutMs);
const request = options.signal
? AbortSignal.any([options.signal, deadline])
: deadline;
const response = await fetch(url, { signal: request });
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return (await response.json()) as T;
}
Conserva la señal del plazo cuando necesites clasificar un fallo. En el código de producción, no la crees dos veces. Devuelve un objeto con deadline y request, o mantén ambas en el mismo ámbito, para que el manejador observe la señal que realmente se envió a fetch.
AbortSignal.any() llegó a Node.js después de AbortSignal.timeout(). Si tu matriz de soporte incluye runtimes antiguos, comprueba la versión documentada antes de publicar este código. Un AbortController controlado con setTimeout es la alternativa cuando necesitas un runtime sin estos métodos estáticos.
¿Cómo distinguir un timeout de una cancelación o un fallo de red?
El objeto de error no siempre explica por qué se detuvo el trabajo. Conserva la señal del plazo y la señal del emisor. En el bloque catch, comprueba cuál se abortó antes de clasificar el fallo para logs, métricas o una política de retry.
type FailureKind = "timeout" | "cancelled" | "network";
function classifyFailure(
error: unknown,
deadline: AbortSignal,
callerSignal?: AbortSignal,
): FailureKind {
if (deadline.aborted) {
return "timeout";
}
if (callerSignal?.aborted) {
return "cancelled";
}
if (error instanceof Error) {
return "network";
}
return "network";
}
Con el timeout nativo, la razón de la señal es un DOMException con nombre TimeoutError, según MDN. La cancelación manual puede usar AbortController.abort(reason) y llevar su propia razón. Observar el estado de las señales sigue siendo útil cuando distintas implementaciones de fetch producen objetos de error diferentes.
El estado HTTP es otro camino. Un 404 o 503 normalmente resuelve la Promise de fetch. No es automáticamente un fallo de red. Comprueba response.ok y convierte el estado en un error de dominio antes de aplicar una política de retry.
El punto de decisión útil no es el nombre del error. Es el límite que controlaste. Un plazo conocido puede tratarse como presupuesto agotado, mientras que una conexión rechazada puede requerir otra acción. Mezclar ambos caminos hace que una política de retry parezca simple y falle durante el primer incidente.
¿El timeout también limita la lectura del cuerpo?
Sí, si la misma señal sigue asociada a fetch mientras consumes el cuerpo. MDN muestra la llamada y la lectura de la respuesta dentro de la misma operación. Esto importa cuando el servidor envía las cabeceras rápido, pero tarda mucho más en producir todos los bytes.
async function readText(url: string, timeoutMs: number): Promise<string> {
const signal = AbortSignal.timeout(timeoutMs);
const response = await fetch(url, { signal });
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.text();
}
No coloques un temporizador solo alrededor del primer await fetch(url). Esa Promise puede resolverse cuando llegan las cabeceras. La lectura de text(), json() o de un stream todavía puede estar activa. La señal debe seguir el límite que quieres controlar.
Si el plazo debe terminar inmediatamente cuando acaba la operación, recuerda que AbortSignal.timeout() no tiene un método para cancelar su propio temporizador. Para código con muchos listeners o plazos largos, MDN recomienda considerar AbortController, setTimeout y clearTimeout, con limpieza en finally.
¿Cómo probar un fetch que expira sin esperar a la red?
Prueba el comportamiento con un servidor HTTP local que controles. El ejemplo ejecutable siguiente envía las cabeceras, espera y solo después envía el cuerpo. Comprueba tanto el plazo de la petición como el plazo durante la lectura.
import { createServer } from "node:http";
const server = createServer((_request, response) => {
response.writeHead(200, { "content-type": "text/plain" });
response.flushHeaders();
setTimeout(() => {
response.end("respuesta tardía");
}, 200);
});
await new Promise<void>((resolve) => server.listen(0, resolve));
const address = server.address();
if (!address || typeof address === "string") {
throw new Error("El servidor no abrió un puerto");
}
try {
const response = await fetch(`http://127.0.0.1:${address.port}`, {
signal: AbortSignal.timeout(30),
});
await response.text();
throw new Error("El timeout debería interrumpir la lectura");
} catch (error) {
if (error instanceof DOMException && error.name === "TimeoutError") {
console.log("timeout verificado");
} else {
throw error;
}
} finally {
server.close();
}
En un repositorio de producción, convierte esta idea en una prueba para el runner que use el equipo. Cubre también una respuesta rápida, una cancelación manual y un error de conexión. No dependas de sleep contra una API pública: las condiciones externas vuelven la prueba lenta y ocultan la causa del fallo.
Ejecuté la lógica equivalente con un servidor local retrasado en el Node.js disponible en el espacio de trabajo. Esa comprobación confirma el camino del timeout y la lectura del cuerpo. No demuestra que todos los proxies, implementaciones de fetch o servidores detengan el trabajo remoto de la misma manera.
¿Se puede reintentar automáticamente después de un timeout?
Solo después de decidir si la operación se puede repetir sin riesgo. El cliente puede dejar de esperar después de que el servidor haya recibido la petición. Esto importa para pagos, creación de registros, generación de informes y cualquier endpoint con efectos externos. Abortar fetch no deshace una transacción remota.
Para una lectura idempotente, un retry limitado puede tener sentido después de clasificar el timeout. Para una escritura, usa una clave de idempotencia o un contrato explícito del servidor antes de reintentar. La guía para probar la idempotencia de una API cubre la protección contra efectos duplicados.
La guía para probar la lógica de retry en JavaScript sin esperar cubre el reloj de la prueba. No sustituye la decisión de producto sobre repetir una petición que quizá ya llegó a su destino.
¿Qué no resuelve AbortSignal.timeout()?
La señal indica que una operación debe detenerse. No hace más rápida la dependencia, no corrige un estado HTTP, no valida JSON ni deshace el trabajo del servidor. Después de un timeout, el trabajo remoto puede seguir activo, según cuándo recibió y procesó la petición.
Tampoco uses un cast de TypeScript para ocultar la respuesta. Después de que la llamada pase el límite de tiempo y transporte, valida el payload antes de tratarlo como dato confiable. Consulta cómo validar JSON externo antes de usarlo en TypeScript para la siguiente frontera.
Preguntas frecuentes
¿fetch rechaza su Promise para un 404?
No necesariamente. Un estado HTTP puede producir una Response resuelta. Comprueba response.ok o response.status y convierte el resultado en un fallo de dominio. Un timeout es una interrupción de la señal, mientras que un 404 es una respuesta recibida del servidor.
¿AbortSignal.timeout() reemplaza a AbortController?
Para un plazo simple, evita crear y limpiar un temporizador manual. Todavía necesitas AbortController cuando el emisor puede cancelar, cuando quieres una razón propia o cuando debes terminar el temporizador explícitamente. Combina las señales cuando el plazo y la cancelación representan decisiones distintas.
¿Un timeout impide que el servidor termine su trabajo?
No. Detiene la espera y el procesamiento observable en el cliente. La petición puede haber llegado al servidor antes del abort. Para efectos externos, usa idempotencia, confirmación de estado o un mecanismo de cancelación que el propio servidor entienda.
Conclusión
El patrón básico es fetch(url, { signal: AbortSignal.timeout(ms) }). Cuando existe cancelación manual, combina las señales con AbortSignal.any(). Si la respuesta llega por partes, conserva la señal durante la lectura del cuerpo. Antes de reintentar, clasifica el fallo y confirma que el efecto remoto sea idempotente.
Este artículo no define un plazo universal ni reemplaza la política de tu servicio. Ofrece un límite explícito para que el resto de la aplicación decida qué hacer cuando una dependencia tarda más de lo permitido por el contrato.
Fuentes consultadas
- Node.js: Global objects, consultada el 28 de septiembre de 2026.
- MDN: AbortSignal.timeout() static method, consultada el 28 de septiembre de 2026.
- WHATWG Fetch issue 951: Add a timeout option, consultada el 28 de septiembre de 2026.
- Stack Overflow: Fetch API request timeout?, consultada el 28 de septiembre de 2026.