concepto · fundamentos · 9 min de lectura
Idempotencia
En HTTP, la idempotencia permite que varias solicitudes idénticas tengan el mismo efecto previsto que una sola, lo que hace más seguros ciertos reintentos.
No requiere conocimientos previos.
Alcance en breve
Cubre
- HTTP method semantics
- retry safety for intended server state
No cubre
- exactly-once delivery guarantees
- database transaction isolation
Supone
- The reader has general programming literacy; HTTP request, response, and retry terminology is introduced in the package.
Resumen
Idempotencia: ejecutar una operación una vez o varias veces produce el mismo resultado final. En HTTP, un método de solicitud es idempotente cuando varias solicitudes idénticas con ese método tienen el mismo efecto previsto en el servidor que una sola solicitud. No exige que cada repetición devuelva el mismo cuerpo de respuesta ni el mismo código de estado, solo el mismo efecto previsto.
Importa porque la comunicación por red falla en momentos inciertos. Un cliente envía una solicitud y el timeout o una conexión interrumpida le impiden saber si el servidor la aplicó. Si la solicitud es idempotente, el cliente puede reenviarla sin pedirle al servidor un efecto adicional al de la primera vez.
Alcance y supuestos
Este paquete cubre la idempotencia como propiedad semántica de los métodos HTTP y por qué esa propiedad hace más seguros ciertos reintentos. También da el contexto mínimo de solicitud, respuesta y reintento para alguien con conocimientos generales de programación.
No cubre entrega exactamente una vez, aislamiento de transacciones, protección frente a cambios concurrentes ni deduplicación de comandos arbitrarios de una aplicación: esas propiedades necesitan contratos separados. Idempotente tampoco significa seguro: DELETE es idempotente y aun así pide eliminar algo.
Modelo mental
Piensa en el botón "Apagar" de un interruptor de luz. Si la luz está encendida y lo presionas, se apaga. Si lo vuelves a presionar, sigue apagada: el estado final no cambia aunque repitas la acción. Esa operación es idempotente.
Compara eso con un botón "Alternar" (toggle): la primera vez enciende, la segunda apaga, la tercera enciende. Cada ejecución cambia el resultado. Esa operación no es idempotente.
El mismo razonamiento aplica a una solicitud HTTP: comparas el efecto previsto en el servidor de una solicitud contra el efecto previsto de varias solicitudes idénticas. Si ambos casos le piden al servidor llegar al mismo resultado, el método es idempotente. La comparación es sobre qué le pides al servidor, no sobre cuántos mensajes llegaron.
Un ejemplo con una API real. Una solicitud establece en email la preferencia de notificaciones de una cuenta. La cuenta, la representación solicitada y la semántica permanecen idénticas:
- El cliente envía la solicitud.
- El servidor la aplica y establece la preferencia en
email. - La respuesta se pierde antes de llegar al cliente, que ahora no sabe si la actualización ocurrió.
- El cliente reenvía la misma solicitud. El estado previsto sigue siendo "preferencia en
email", igual que en el paso 2.
La segunda solicitud no pide un cambio adicional al que pidió la primera: es el botón "Apagar", no el botón "Alternar". Esto es distinto de un comando como "agregá este evento", donde cada solicitud idéntica le pide al servidor sumar un evento más — ahí sí importa cuántas veces la enviaste.
Las respuestas igual pueden variar. La primera solicitud puede devolver éxito, mientras que una posterior puede indicar que una precondición HTTP (una condición adjunta a la solicitud) ya no se cumple, o que otro cliente modificó el recurso. El servidor también puede registrar cada solicitud por separado. RFC 9110 permite esas diferencias porque la idempotencia habla del efecto previsto, no de la respuesta exacta ni de los efectos secundarios internos.
Uso práctico
Por qué importa en la práctica, cuando un cliente decide si reintentar una solicitud:
- ✅ Reintentar un
PUTo unDELETEes seguro: la repetición no pide un efecto adicional al de la primera solicitud. - ✅ Un cliente puede automatizar el reintento de una solicitud idempotente sin arriesgar un cambio no pedido.
- ❌ Reintentar un
POSTde pago sin una clave de idempotencia puede cobrar dos veces. - ❌ Reintentar un comando "agregar evento" o "crear pedido" sin protección adicional puede duplicarlo.
Ejemplo trabajado: reintentar un PUT cuya respuesta se perdió
Una API permite establecer el canal de notificaciones de una cuenta mediante PUT /users/42/preferences. El estado inicial es sms y el cliente quiere reemplazarlo por email. El servidor aplica la primera solicitud, pero la conexión se interrumpe antes de que el cliente reciba la respuesta. El cliente sólo sabe que el resultado es incierto.
La solicitud que se puede repetir mantiene el mismo método, recurso y representación:
PUT /users/42/preferences HTTP/1.1
Host: localhost:3000
Content-Type: application/json
Content-Length: 25
{"notifications":"email"}
La lógica central del servidor detrás del PUT asigna un valor conocido. handledRequests representa un efecto secundario permitido, como una métrica o un registro, y permite observar que el servidor sí procesó dos solicitudes:
type Channel = "email" | "sms";
let storedPreference: Channel = "sms";
let handledRequests = 0;
function putPreference(channel: Channel) {
handledRequests += 1;
storedPreference = channel;
return { storedPreference, handledRequests };
}
console.log(putPreference("email"));
console.log(putPreference("email"));
La salida esperada muestra valores distintos en handledRequests, pero el mismo estado previsto en storedPreference:
{ storedPreference: 'email', handledRequests: 1 }
{ storedPreference: 'email', handledRequests: 2 }
El siguiente cliente parcial para Node.js 24 usa fetch integrado. Con una URL y una solicitud conocidas como válidas, el primer rechazo representa la falla de comunicación incierta del caso. La segunda llamada se ejecuta dentro del catch, pero no está protegida por otro bloque try/catch; por eso, si también falla, el error se propaga y no se produce un tercer reintento automático:
const url = "http://localhost:3000/users/42/preferences";
const request: RequestInit = {
method: "PUT",
headers: { "content-type": "application/json" },
body: JSON.stringify({ notifications: "email" }),
};
const send = () => fetch(url, request);
let response: Response;
try {
response = await send();
} catch {
response = await send();
}
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());
Después del primer PUT, la preferencia queda en email. Después del reintento idéntico, sigue en email: no existe un cambio de preferencia adicional. En cambio, si la operación pretendiera agregar un nuevo elemento a una lista por cada solicitud, dos solicitudes idénticas pedirían dos elementos y ese contrato no sería idempotente. En producción, el cliente también debe clasificar qué errores de comunicación son reintentables y respetar autorización, precondiciones y concurrencia.
Aplicar la regla fuera del ejemplo
Empieza por el contrato del método. RFC 9110 define PUT, DELETE y los métodos seguros como idempotentes. Un método seguro, como GET, es de solo lectura. La idempotencia es más amplia: un método idempotente sí puede cambiar estado. DELETE pide eliminar algo, pero repetir la misma solicitud no pide otra eliminación además de la primera.
Si la conexión falla antes de recibir respuesta, un cliente puede reintentar automáticamente una solicitud idempotente: la repetición conserva el efecto previsto. Igual, la idempotencia no garantiza que el reintento tenga éxito. La autorización puede expirar, una precondición puede fallar, u otro cliente puede haber modificado el recurso mientras tanto.
No asumas idempotencia por el nombre del endpoint ni por lo inofensivo que parezca. Un pago, una notificación o un "agregar elemento" no son idempotentes si repetir la solicitud pide otro efecto además del primero. Por eso muchas APIs de pago agregan una Idempotency-Key: el cliente genera una clave única por operación y la envía en cada intento. El servidor la guarda; si ve la misma clave dos veces, no vuelve a procesar el pago y devuelve la respuesta original. Es una solución de aplicación, no una garantía del método HTTP en sí.
Antes de reintentar automáticamente una solicitud cuyo método no es idempotente, necesitas saber que esa solicitud específica sí lo es (por ejemplo, con una Idempotency-Key), o tener forma de detectar que nunca se aplicó. Si no, no asumas que repetirla es seguro. Una persona puede inspeccionar el recurso y decidir reintentar a mano; eso es distinto de automatizarlo.
Un cliente no debería reintentar automáticamente algo que ya falló en un reintento automático anterior. Un proxy HTTP tampoco debe reintentar automáticamente una solicitud no idempotente. Estos límites evitan que una falla incierta se convierta en una política de reintento sin fin.
Usa tres preguntas al evaluar una política de reintentos:
- ¿Las solicitudes repetidas mantienen el mismo objetivo y la misma semántica prevista?
- ¿Repetirlas pide algún efecto previsto adicional al de una sola solicitud?
- ¿La autorización, las precondiciones, la concurrencia o las reglas de la aplicación pueden hacer que un reintento automático sea inapropiado aunque el método sea idempotente?
Estas preguntas mantienen acotada la garantía. La idempotencia ayuda a razonar sobre una intención repetida después de una comunicación incierta, pero no reemplaza el resto del diseño de consistencia, concurrencia y manejo de fallas de una API.
Evidencia
- RFC 9110, Sección 9.2.1: Métodos seguros distingue la semántica solicitada de solo lectura de la idempotencia.
- RFC 9110, Sección 9.2.2: Métodos idempotentes define la idempotencia según el efecto previsto en el servidor y especifica cuándo un cliente puede reintentar automáticamente.
- RFC 9110, Sección 9.3.4: PUT señala que un
PUTpuede tener efectos sobre otros recursos sin perder la semántica de su método. - RFC 9110, Sección 9.3.5: DELETE define
DELETEen términos de eliminar la asociación entre el recurso objetivo y su funcionalidad actual. - Documentación de Node.js 24:
fetchdocumenta el cliente HTTP integrado usado en el ejemplo de TypeScript.
Fuentes citadas
- RFC 9110: HTTP Semantics, Section 9.2.2 (Primaria, 14-07-2026)
- Node.js v24 API documentation, Fetch global (Oficial, 18-07-2026)